From 22c7ce0162f2d14a7340e00e93697780c91d2087 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 23 Jun 2015 19:20:20 +0200 Subject: cmd/geth: version bump 0.9.33 --- cmd/geth/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 5a0f523a6..963aced15 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -46,7 +46,7 @@ import ( const ( ClientIdentifier = "Geth" - Version = "0.9.32" + Version = "0.9.33" ) var ( -- cgit v1.2.3 From 821e01b0139eee9bfab9647e4ac1f2d6f1fb01bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 19 Jun 2015 18:13:49 +0300 Subject: cmd/geth, eth/fetcher: initial metrics support Conflicts: cmd/geth/admin.go --- cmd/geth/admin.go | 960 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 960 insertions(+) create mode 100644 cmd/geth/admin.go (limited to 'cmd') diff --git a/cmd/geth/admin.go b/cmd/geth/admin.go new file mode 100644 index 000000000..0d5e02523 --- /dev/null +++ b/cmd/geth/admin.go @@ -0,0 +1,960 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "strconv" + "time" + + "github.com/rcrowley/go-metrics" + + "github.com/ethereum/ethash" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/compiler" + "github.com/ethereum/go-ethereum/common/natspec" + "github.com/ethereum/go-ethereum/common/resolver" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/xeth" + "github.com/robertkrimen/otto" + "gopkg.in/fatih/set.v0" +) + +/* +node admin bindings +*/ + +func (js *jsre) adminBindings() { + ethO, _ := js.re.Get("eth") + eth := ethO.Object() + eth.Set("pendingTransactions", js.pendingTransactions) + eth.Set("resend", js.resend) + eth.Set("sign", js.sign) + + js.re.Set("admin", struct{}{}) + t, _ := js.re.Get("admin") + admin := t.Object() + admin.Set("addPeer", js.addPeer) + admin.Set("startRPC", js.startRPC) + admin.Set("stopRPC", js.stopRPC) + admin.Set("nodeInfo", js.nodeInfo) + admin.Set("peers", js.peers) + admin.Set("newAccount", js.newAccount) + admin.Set("unlock", js.unlock) + admin.Set("import", js.importChain) + admin.Set("export", js.exportChain) + admin.Set("verbosity", js.verbosity) + admin.Set("progress", js.syncProgress) + admin.Set("setSolc", js.setSolc) + + admin.Set("contractInfo", struct{}{}) + t, _ = admin.Get("contractInfo") + cinfo := t.Object() + // newRegistry officially not documented temporary option + cinfo.Set("start", js.startNatSpec) + cinfo.Set("stop", js.stopNatSpec) + cinfo.Set("newRegistry", js.newRegistry) + cinfo.Set("get", js.getContractInfo) + cinfo.Set("register", js.register) + cinfo.Set("registerUrl", js.registerUrl) + // cinfo.Set("verify", js.verify) + + admin.Set("miner", struct{}{}) + t, _ = admin.Get("miner") + miner := t.Object() + miner.Set("start", js.startMining) + miner.Set("stop", js.stopMining) + miner.Set("hashrate", js.hashrate) + miner.Set("setExtra", js.setExtra) + miner.Set("setGasPrice", js.setGasPrice) + miner.Set("startAutoDAG", js.startAutoDAG) + miner.Set("stopAutoDAG", js.stopAutoDAG) + miner.Set("makeDAG", js.makeDAG) + + admin.Set("txPool", struct{}{}) + t, _ = admin.Get("txPool") + txPool := t.Object() + txPool.Set("pending", js.allPendingTransactions) + txPool.Set("queued", js.allQueuedTransactions) + + admin.Set("debug", struct{}{}) + t, _ = admin.Get("debug") + debug := t.Object() + js.re.Set("sleep", js.sleep) + debug.Set("backtrace", js.backtrace) + debug.Set("printBlock", js.printBlock) + debug.Set("dumpBlock", js.dumpBlock) + debug.Set("getBlockRlp", js.getBlockRlp) + debug.Set("setHead", js.setHead) + debug.Set("processBlock", js.debugBlock) + debug.Set("seedhash", js.seedHash) + debug.Set("insertBlock", js.insertBlockRlp) + // undocumented temporary + debug.Set("waitForBlocks", js.waitForBlocks) + + admin.Set("metrics", js.metrics) +} + +// generic helper to getBlock by Number/Height or Hex depending on autodetected input +// if argument is missing the current block is returned +// if block is not found or there is problem with decoding +// the appropriate value is returned and block is guaranteed to be nil +func (js *jsre) getBlock(call otto.FunctionCall) (*types.Block, error) { + var block *types.Block + if len(call.ArgumentList) > 0 { + if call.Argument(0).IsNumber() { + num, _ := call.Argument(0).ToInteger() + block = js.ethereum.ChainManager().GetBlockByNumber(uint64(num)) + } else if call.Argument(0).IsString() { + hash, _ := call.Argument(0).ToString() + block = js.ethereum.ChainManager().GetBlock(common.HexToHash(hash)) + } else { + return nil, errors.New("invalid argument for dump. Either hex string or number") + } + } else { + block = js.ethereum.ChainManager().CurrentBlock() + } + + if block == nil { + return nil, errors.New("block not found") + } + return block, nil +} + +func (js *jsre) seedHash(call otto.FunctionCall) otto.Value { + if len(call.ArgumentList) > 0 { + if call.Argument(0).IsNumber() { + num, _ := call.Argument(0).ToInteger() + hash, err := ethash.GetSeedHash(uint64(num)) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + v, _ := call.Otto.ToValue(fmt.Sprintf("0x%x", hash)) + return v + } else { + fmt.Println("arg not a number") + } + } else { + fmt.Println("requires number argument") + } + + return otto.UndefinedValue() +} + +func (js *jsre) allPendingTransactions(call otto.FunctionCall) otto.Value { + txs := js.ethereum.TxPool().GetTransactions() + + ltxs := make([]*tx, len(txs)) + for i, tx := range txs { + // no need to check err + ltxs[i] = newTx(tx) + } + + v, _ := call.Otto.ToValue(ltxs) + return v +} + +func (js *jsre) allQueuedTransactions(call otto.FunctionCall) otto.Value { + txs := js.ethereum.TxPool().GetQueuedTransactions() + + ltxs := make([]*tx, len(txs)) + for i, tx := range txs { + // no need to check err + ltxs[i] = newTx(tx) + } + + v, _ := call.Otto.ToValue(ltxs) + return v +} + +func (js *jsre) pendingTransactions(call otto.FunctionCall) otto.Value { + txs := js.ethereum.TxPool().GetTransactions() + + // grab the accounts from the account manager. This will help with determening which + // transactions should be returned. + accounts, err := js.ethereum.AccountManager().Accounts() + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + // Add the accouns to a new set + accountSet := set.New() + for _, account := range accounts { + accountSet.Add(account.Address) + } + + //ltxs := make([]*tx, len(txs)) + var ltxs []*tx + for _, tx := range txs { + if from, _ := tx.From(); accountSet.Has(from) { + ltxs = append(ltxs, newTx(tx)) + } + } + + v, _ := call.Otto.ToValue(ltxs) + return v +} + +func (js *jsre) resend(call otto.FunctionCall) otto.Value { + if len(call.ArgumentList) == 0 { + fmt.Println("first argument must be a transaction") + return otto.FalseValue() + } + + v, err := call.Argument(0).Export() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + if tx, ok := v.(*tx); ok { + gl, gp := tx.GasLimit, tx.GasPrice + if len(call.ArgumentList) > 1 { + gp = call.Argument(1).String() + } + if len(call.ArgumentList) > 2 { + gl = call.Argument(2).String() + } + + ret, err := js.xeth.Transact(tx.From, tx.To, tx.Nonce, tx.Value, gl, gp, tx.Data) + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + js.ethereum.TxPool().RemoveTransactions(types.Transactions{tx.tx}) + + v, _ := call.Otto.ToValue(ret) + return v + } + + fmt.Println("first argument must be a transaction") + return otto.FalseValue() +} + +func (js *jsre) sign(call otto.FunctionCall) otto.Value { + if len(call.ArgumentList) != 2 { + fmt.Println("requires 2 arguments: eth.sign(signer, data)") + return otto.UndefinedValue() + } + signer, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + data, err := call.Argument(1).ToString() + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + signed, err := js.xeth.Sign(signer, data, false) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + v, _ := call.Otto.ToValue(signed) + return v +} + +func (js *jsre) debugBlock(call otto.FunctionCall) otto.Value { + block, err := js.getBlock(call) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + tstart := time.Now() + old := vm.Debug + + if len(call.ArgumentList) > 1 { + vm.Debug, _ = call.Argument(1).ToBoolean() + } + + _, err = js.ethereum.BlockProcessor().RetryProcess(block) + if err != nil { + fmt.Println(err) + r, _ := call.Otto.ToValue(map[string]interface{}{"success": false, "time": time.Since(tstart).Seconds()}) + return r + } + vm.Debug = old + + r, _ := call.Otto.ToValue(map[string]interface{}{"success": true, "time": time.Since(tstart).Seconds()}) + return r +} + +func (js *jsre) insertBlockRlp(call otto.FunctionCall) otto.Value { + tstart := time.Now() + + var block types.Block + if call.Argument(0).IsString() { + blockRlp, _ := call.Argument(0).ToString() + err := rlp.DecodeBytes(common.Hex2Bytes(blockRlp), &block) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + } + + old := vm.Debug + vm.Debug = true + _, err := js.ethereum.BlockProcessor().RetryProcess(&block) + if err != nil { + fmt.Println(err) + r, _ := call.Otto.ToValue(map[string]interface{}{"success": false, "time": time.Since(tstart).Seconds()}) + return r + } + vm.Debug = old + + r, _ := call.Otto.ToValue(map[string]interface{}{"success": true, "time": time.Since(tstart).Seconds()}) + return r +} + +func (js *jsre) setHead(call otto.FunctionCall) otto.Value { + block, err := js.getBlock(call) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + js.ethereum.ChainManager().SetHead(block) + return otto.UndefinedValue() +} + +func (js *jsre) syncProgress(call otto.FunctionCall) otto.Value { + pending, cached, importing, eta := js.ethereum.Downloader().Stats() + v, _ := call.Otto.ToValue(map[string]interface{}{ + "pending": pending, + "cached": cached, + "importing": importing, + "estimate": (eta / time.Second * time.Second).String(), + }) + return v +} + +func (js *jsre) getBlockRlp(call otto.FunctionCall) otto.Value { + block, err := js.getBlock(call) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + encoded, _ := rlp.EncodeToBytes(block) + v, _ := call.Otto.ToValue(fmt.Sprintf("%x", encoded)) + return v +} + +func (js *jsre) setExtra(call otto.FunctionCall) otto.Value { + extra, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + if len(extra) > 1024 { + fmt.Println("error: cannot exceed 1024 bytes") + return otto.UndefinedValue() + } + + js.ethereum.Miner().SetExtra([]byte(extra)) + return otto.UndefinedValue() +} + +func (js *jsre) setGasPrice(call otto.FunctionCall) otto.Value { + gasPrice, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + js.ethereum.Miner().SetGasPrice(common.String2Big(gasPrice)) + return otto.UndefinedValue() +} + +func (js *jsre) hashrate(call otto.FunctionCall) otto.Value { + v, _ := call.Otto.ToValue(js.ethereum.Miner().HashRate()) + return v +} + +func (js *jsre) makeDAG(call otto.FunctionCall) otto.Value { + blockNumber, err := call.Argument(1).ToInteger() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + err = ethash.MakeDAG(uint64(blockNumber), "") + if err != nil { + return otto.FalseValue() + } + return otto.TrueValue() +} + +func (js *jsre) startAutoDAG(otto.FunctionCall) otto.Value { + js.ethereum.StartAutoDAG() + return otto.TrueValue() +} + +func (js *jsre) stopAutoDAG(otto.FunctionCall) otto.Value { + js.ethereum.StopAutoDAG() + return otto.TrueValue() +} + +func (js *jsre) backtrace(call otto.FunctionCall) otto.Value { + tracestr, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + glog.GetTraceLocation().Set(tracestr) + + return otto.UndefinedValue() +} + +func (js *jsre) verbosity(call otto.FunctionCall) otto.Value { + v, err := call.Argument(0).ToInteger() + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + glog.SetV(int(v)) + return otto.UndefinedValue() +} + +func (js *jsre) startMining(call otto.FunctionCall) otto.Value { + var ( + threads int64 + err error + ) + + if len(call.ArgumentList) > 0 { + threads, err = call.Argument(0).ToInteger() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + } else { + threads = int64(js.ethereum.MinerThreads) + } + + // switch on DAG autogeneration when miner starts + js.ethereum.StartAutoDAG() + + err = js.ethereum.StartMining(int(threads)) + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + return otto.TrueValue() +} + +func (js *jsre) stopMining(call otto.FunctionCall) otto.Value { + js.ethereum.StopMining() + js.ethereum.StopAutoDAG() + return otto.TrueValue() +} + +func (js *jsre) startRPC(call otto.FunctionCall) otto.Value { + addr, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + port, err := call.Argument(1).ToInteger() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + corsDomain := js.corsDomain + if len(call.ArgumentList) > 2 { + corsDomain, err = call.Argument(2).ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + } + + config := rpc.RpcConfig{ + ListenAddress: addr, + ListenPort: uint(port), + CorsDomain: corsDomain, + } + + xeth := xeth.New(js.ethereum, nil) + err = rpc.Start(xeth, config) + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + return otto.TrueValue() +} + +func (js *jsre) stopRPC(call otto.FunctionCall) otto.Value { + if rpc.Stop() == nil { + return otto.TrueValue() + } + return otto.FalseValue() +} + +func (js *jsre) addPeer(call otto.FunctionCall) otto.Value { + nodeURL, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + err = js.ethereum.AddPeer(nodeURL) + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + return otto.TrueValue() +} + +func (js *jsre) unlock(call otto.FunctionCall) otto.Value { + addr, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + seconds, err := call.Argument(2).ToInteger() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + if seconds == 0 { + seconds = accounts.DefaultAccountUnlockDuration + } + + arg := call.Argument(1) + var passphrase string + if arg.IsUndefined() { + fmt.Println("Please enter a passphrase now.") + passphrase, err = utils.PromptPassword("Passphrase: ", true) + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + } else { + passphrase, err = arg.ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + } + am := js.ethereum.AccountManager() + err = am.TimedUnlock(common.HexToAddress(addr), passphrase, time.Duration(seconds)*time.Second) + if err != nil { + fmt.Printf("Unlock account failed '%v'\n", err) + return otto.FalseValue() + } + return otto.TrueValue() +} + +func (js *jsre) newAccount(call otto.FunctionCall) otto.Value { + arg := call.Argument(0) + var passphrase string + if arg.IsUndefined() { + fmt.Println("The new account will be encrypted with a passphrase.") + fmt.Println("Please enter a passphrase now.") + auth, err := utils.PromptPassword("Passphrase: ", true) + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + confirm, err := utils.PromptPassword("Repeat Passphrase: ", false) + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + if auth != confirm { + fmt.Println("Passphrases did not match.") + return otto.FalseValue() + } + passphrase = auth + } else { + var err error + passphrase, err = arg.ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + } + acct, err := js.ethereum.AccountManager().NewAccount(passphrase) + if err != nil { + fmt.Printf("Could not create the account: %v", err) + return otto.UndefinedValue() + } + v, _ := call.Otto.ToValue(acct.Address.Hex()) + return v +} + +func (js *jsre) nodeInfo(call otto.FunctionCall) otto.Value { + v, _ := call.Otto.ToValue(js.ethereum.NodeInfo()) + return v +} + +func (js *jsre) peers(call otto.FunctionCall) otto.Value { + v, _ := call.Otto.ToValue(js.ethereum.PeersInfo()) + return v +} + +func (js *jsre) importChain(call otto.FunctionCall) otto.Value { + if len(call.ArgumentList) == 0 { + fmt.Println("require file name. admin.importChain(filename)") + return otto.FalseValue() + } + fn, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + if err := utils.ImportChain(js.ethereum.ChainManager(), fn); err != nil { + fmt.Println("Import error: ", err) + return otto.FalseValue() + } + return otto.TrueValue() +} + +func (js *jsre) exportChain(call otto.FunctionCall) otto.Value { + if len(call.ArgumentList) == 0 { + fmt.Println("require file name: admin.exportChain(filename)") + return otto.FalseValue() + } + + fn, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + if err := utils.ExportChain(js.ethereum.ChainManager(), fn); err != nil { + fmt.Println(err) + return otto.FalseValue() + } + return otto.TrueValue() +} + +func (js *jsre) printBlock(call otto.FunctionCall) otto.Value { + block, err := js.getBlock(call) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + fmt.Println(block) + + return otto.UndefinedValue() +} + +func (js *jsre) dumpBlock(call otto.FunctionCall) otto.Value { + block, err := js.getBlock(call) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + statedb := state.New(block.Root(), js.ethereum.StateDb()) + dump := statedb.RawDump() + v, _ := call.Otto.ToValue(dump) + return v +} + +func (js *jsre) waitForBlocks(call otto.FunctionCall) otto.Value { + if len(call.ArgumentList) > 2 { + fmt.Println("requires 0, 1 or 2 arguments: admin.debug.waitForBlock(minHeight, timeout)") + return otto.FalseValue() + } + var n, timeout int64 + var timer <-chan time.Time + var height *big.Int + var err error + args := len(call.ArgumentList) + if args == 2 { + timeout, err = call.Argument(1).ToInteger() + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + timer = time.NewTimer(time.Duration(timeout) * time.Second).C + } + if args >= 1 { + n, err = call.Argument(0).ToInteger() + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + height = big.NewInt(n) + } + + if args == 0 { + height = js.xeth.CurrentBlock().Number() + height.Add(height, common.Big1) + } + + wait := js.wait + js.wait <- height + select { + case <-timer: + // if times out make sure the xeth loop does not block + go func() { + select { + case wait <- nil: + case <-wait: + } + }() + return otto.UndefinedValue() + case height = <-wait: + } + v, _ := call.Otto.ToValue(height.Uint64()) + return v +} + +func (js *jsre) metrics(call otto.FunctionCall) otto.Value { + // Iterate over all the metrics, and just dump for now + counters := make(map[string]interface{}) + metrics.DefaultRegistry.Each(func(name string, metric interface{}) { + switch metric := metric.(type) { + case metrics.Meter: + counters[name+"( 1 min)"] = int(metric.Rate1() * 60) + counters[name+"( 5 min)"] = int(metric.Rate5() * 300) + counters[name+"(15 min)"] = int(metric.Rate15() * 900) + + default: + counters[name] = "Unknown metric type" + } + }) + // Flatten the counters into some metrics and return + v, _ := call.Otto.ToValue(counters) + return v +} + +func (js *jsre) sleep(call otto.FunctionCall) otto.Value { + sec, err := call.Argument(0).ToInteger() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + time.Sleep(time.Duration(sec) * time.Second) + return otto.UndefinedValue() +} + +func (js *jsre) setSolc(call otto.FunctionCall) otto.Value { + if len(call.ArgumentList) != 1 { + fmt.Println("needs 1 argument: admin.contractInfo.setSolc(solcPath)") + return otto.FalseValue() + } + solcPath, err := call.Argument(0).ToString() + if err != nil { + return otto.FalseValue() + } + solc, err := js.xeth.SetSolc(solcPath) + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + fmt.Println(solc.Info()) + return otto.TrueValue() +} + +func (js *jsre) register(call otto.FunctionCall) otto.Value { + if len(call.ArgumentList) != 4 { + fmt.Println("requires 4 arguments: admin.contractInfo.register(fromaddress, contractaddress, contract, filename)") + return otto.UndefinedValue() + } + sender, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + address, err := call.Argument(1).ToString() + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + raw, err := call.Argument(2).Export() + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + jsonraw, err := json.Marshal(raw) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + var contract compiler.Contract + err = json.Unmarshal(jsonraw, &contract) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + filename, err := call.Argument(3).ToString() + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + contenthash, err := compiler.ExtractInfo(&contract, filename) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + // sender and contract address are passed as hex strings + codeb := js.xeth.CodeAtBytes(address) + codehash := common.BytesToHash(crypto.Sha3(codeb)) + + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + registry := resolver.New(js.xeth) + + _, err = registry.RegisterContentHash(common.HexToAddress(sender), codehash, contenthash) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + v, _ := call.Otto.ToValue(contenthash.Hex()) + return v +} + +func (js *jsre) registerUrl(call otto.FunctionCall) otto.Value { + if len(call.ArgumentList) != 3 { + fmt.Println("requires 3 arguments: admin.contractInfo.register(fromaddress, contenthash, filename)") + return otto.FalseValue() + } + sender, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + contenthash, err := call.Argument(1).ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + url, err := call.Argument(2).ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + registry := resolver.New(js.xeth) + + _, err = registry.RegisterUrl(common.HexToAddress(sender), common.HexToHash(contenthash), url) + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + return otto.TrueValue() +} + +func (js *jsre) getContractInfo(call otto.FunctionCall) otto.Value { + if len(call.ArgumentList) != 1 { + fmt.Println("requires 1 argument: admin.contractInfo.register(contractaddress)") + return otto.FalseValue() + } + addr, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + infoDoc, err := natspec.FetchDocsForContract(addr, js.xeth, ds) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + var info compiler.ContractInfo + err = json.Unmarshal(infoDoc, &info) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + v, _ := call.Otto.ToValue(info) + return v +} + +func (js *jsre) startNatSpec(call otto.FunctionCall) otto.Value { + js.ethereum.NatSpec = true + return otto.TrueValue() +} + +func (js *jsre) stopNatSpec(call otto.FunctionCall) otto.Value { + js.ethereum.NatSpec = false + return otto.TrueValue() +} + +func (js *jsre) newRegistry(call otto.FunctionCall) otto.Value { + + if len(call.ArgumentList) != 1 { + fmt.Println("requires 1 argument: admin.contractInfo.newRegistry(adminaddress)") + return otto.FalseValue() + } + addr, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + registry := resolver.New(js.xeth) + err = registry.CreateContracts(common.HexToAddress(addr)) + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + return otto.TrueValue() +} + +// internal transaction type which will allow us to resend transactions using `eth.resend` +type tx struct { + tx *types.Transaction + + To string + From string + Nonce string + Value string + Data string + GasLimit string + GasPrice string +} + +func newTx(t *types.Transaction) *tx { + from, _ := t.From() + var to string + if t := t.To(); t != nil { + to = t.Hex() + } + + return &tx{ + tx: t, + To: to, + From: from.Hex(), + Value: t.Amount.String(), + Nonce: strconv.Itoa(int(t.Nonce())), + Data: "0x" + common.Bytes2Hex(t.Data()), + GasLimit: t.GasLimit.String(), + GasPrice: t.GasPrice().String(), + } +} -- cgit v1.2.3 From b426301467304a6c047df9baa033a042ddf3c4bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Sun, 21 Jun 2015 20:23:51 +0300 Subject: cmd/geth, eth/fetcher: polish metrics reporting, add some more --- cmd/geth/admin.go | 51 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) (limited to 'cmd') diff --git a/cmd/geth/admin.go b/cmd/geth/admin.go index 0d5e02523..7d8780ef0 100644 --- a/cmd/geth/admin.go +++ b/cmd/geth/admin.go @@ -6,10 +6,9 @@ import ( "fmt" "math/big" "strconv" + "strings" "time" - "github.com/rcrowley/go-metrics" - "github.com/ethereum/ethash" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/cmd/utils" @@ -25,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/xeth" + "github.com/rcrowley/go-metrics" "github.com/robertkrimen/otto" "gopkg.in/fatih/set.v0" ) @@ -723,17 +723,56 @@ func (js *jsre) waitForBlocks(call otto.FunctionCall) otto.Value { } func (js *jsre) metrics(call otto.FunctionCall) otto.Value { + // Create a rate formatter + units := []string{"", "K", "M", "G", "T", "E", "P"} + round := func(value float64, prec int) string { + unit := 0 + for value >= 1000 { + unit, value, prec = unit+1, value/1000, 2 + } + return fmt.Sprintf(fmt.Sprintf("%%.%df%s", prec, units[unit]), value) + } + format := func(total float64, rate float64) string { + return fmt.Sprintf("%s (%s/s)", round(total, 0), round(rate, 2)) + } + // Iterate over all the metrics, and just dump for now counters := make(map[string]interface{}) metrics.DefaultRegistry.Each(func(name string, metric interface{}) { + // Create or retrieve the counter hierarchy for this metric + root, parts := counters, strings.Split(name, "/") + for _, part := range parts[:len(parts)-1] { + if _, ok := root[part]; !ok { + root[part] = make(map[string]interface{}) + } + root = root[part].(map[string]interface{}) + } + name = parts[len(parts)-1] + + // Fill the counter with the metric details switch metric := metric.(type) { case metrics.Meter: - counters[name+"( 1 min)"] = int(metric.Rate1() * 60) - counters[name+"( 5 min)"] = int(metric.Rate5() * 300) - counters[name+"(15 min)"] = int(metric.Rate15() * 900) + root[name] = map[string]interface{}{ + "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), + "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), + "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), + "Overall": format(float64(metric.Count()), metric.RateMean()), + } + + case metrics.Timer: + root[name] = map[string]interface{}{ + "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), + "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), + "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), + "Overall": format(float64(metric.Count()), metric.RateMean()), + "Perc01": round(metric.Percentile(1), 2), + "Perc05": round(metric.Percentile(5), 2), + "Perc25": round(metric.Percentile(25), 2), + "Perc90": round(metric.Percentile(90), 2), + } default: - counters[name] = "Unknown metric type" + root[name] = "Unknown metric type" } }) // Flatten the counters into some metrics and return -- cgit v1.2.3 From 7f92e708c504e1c4a7382c68b5a4ed68db9c8deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Sun, 21 Jun 2015 22:10:24 +0300 Subject: cmd/geth, core: impl. percentile reporting, instrument insertions --- cmd/geth/admin.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'cmd') diff --git a/cmd/geth/admin.go b/cmd/geth/admin.go index 7d8780ef0..0c26cc97c 100644 --- a/cmd/geth/admin.go +++ b/cmd/geth/admin.go @@ -735,7 +735,6 @@ func (js *jsre) metrics(call otto.FunctionCall) otto.Value { format := func(total float64, rate float64) string { return fmt.Sprintf("%s (%s/s)", round(total, 0), round(rate, 2)) } - // Iterate over all the metrics, and just dump for now counters := make(map[string]interface{}) metrics.DefaultRegistry.Each(func(name string, metric interface{}) { @@ -756,7 +755,7 @@ func (js *jsre) metrics(call otto.FunctionCall) otto.Value { "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), - "Overall": format(float64(metric.Count()), metric.RateMean()), + "Total": format(float64(metric.Count()), metric.RateMean()), } case metrics.Timer: @@ -764,11 +763,16 @@ func (js *jsre) metrics(call otto.FunctionCall) otto.Value { "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), - "Overall": format(float64(metric.Count()), metric.RateMean()), - "Perc01": round(metric.Percentile(1), 2), - "Perc05": round(metric.Percentile(5), 2), - "Perc25": round(metric.Percentile(25), 2), - "Perc90": round(metric.Percentile(90), 2), + "Count": format(float64(metric.Count()), metric.RateMean()), + "Maximum": time.Duration(metric.Max()).String(), + "Minimum": time.Duration(metric.Min()).String(), + "Percentile": map[string]interface{}{ + "20": time.Duration(metric.Percentile(0.2)).String(), + "50": time.Duration(metric.Percentile(0.5)).String(), + "80": time.Duration(metric.Percentile(0.8)).String(), + "95": time.Duration(metric.Percentile(0.95)).String(), + "99": time.Duration(metric.Percentile(0.99)).String(), + }, } default: -- cgit v1.2.3 From bde2ff034317db977354e0855e6406f9428899ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 23 Jun 2015 19:12:48 +0300 Subject: cmd/geth, rpc/api: move the metrics into the new console --- cmd/geth/admin.go | 1003 ----------------------------------------------------- 1 file changed, 1003 deletions(-) delete mode 100644 cmd/geth/admin.go (limited to 'cmd') diff --git a/cmd/geth/admin.go b/cmd/geth/admin.go deleted file mode 100644 index 0c26cc97c..000000000 --- a/cmd/geth/admin.go +++ /dev/null @@ -1,1003 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "fmt" - "math/big" - "strconv" - "strings" - "time" - - "github.com/ethereum/ethash" - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/compiler" - "github.com/ethereum/go-ethereum/common/natspec" - "github.com/ethereum/go-ethereum/common/resolver" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/xeth" - "github.com/rcrowley/go-metrics" - "github.com/robertkrimen/otto" - "gopkg.in/fatih/set.v0" -) - -/* -node admin bindings -*/ - -func (js *jsre) adminBindings() { - ethO, _ := js.re.Get("eth") - eth := ethO.Object() - eth.Set("pendingTransactions", js.pendingTransactions) - eth.Set("resend", js.resend) - eth.Set("sign", js.sign) - - js.re.Set("admin", struct{}{}) - t, _ := js.re.Get("admin") - admin := t.Object() - admin.Set("addPeer", js.addPeer) - admin.Set("startRPC", js.startRPC) - admin.Set("stopRPC", js.stopRPC) - admin.Set("nodeInfo", js.nodeInfo) - admin.Set("peers", js.peers) - admin.Set("newAccount", js.newAccount) - admin.Set("unlock", js.unlock) - admin.Set("import", js.importChain) - admin.Set("export", js.exportChain) - admin.Set("verbosity", js.verbosity) - admin.Set("progress", js.syncProgress) - admin.Set("setSolc", js.setSolc) - - admin.Set("contractInfo", struct{}{}) - t, _ = admin.Get("contractInfo") - cinfo := t.Object() - // newRegistry officially not documented temporary option - cinfo.Set("start", js.startNatSpec) - cinfo.Set("stop", js.stopNatSpec) - cinfo.Set("newRegistry", js.newRegistry) - cinfo.Set("get", js.getContractInfo) - cinfo.Set("register", js.register) - cinfo.Set("registerUrl", js.registerUrl) - // cinfo.Set("verify", js.verify) - - admin.Set("miner", struct{}{}) - t, _ = admin.Get("miner") - miner := t.Object() - miner.Set("start", js.startMining) - miner.Set("stop", js.stopMining) - miner.Set("hashrate", js.hashrate) - miner.Set("setExtra", js.setExtra) - miner.Set("setGasPrice", js.setGasPrice) - miner.Set("startAutoDAG", js.startAutoDAG) - miner.Set("stopAutoDAG", js.stopAutoDAG) - miner.Set("makeDAG", js.makeDAG) - - admin.Set("txPool", struct{}{}) - t, _ = admin.Get("txPool") - txPool := t.Object() - txPool.Set("pending", js.allPendingTransactions) - txPool.Set("queued", js.allQueuedTransactions) - - admin.Set("debug", struct{}{}) - t, _ = admin.Get("debug") - debug := t.Object() - js.re.Set("sleep", js.sleep) - debug.Set("backtrace", js.backtrace) - debug.Set("printBlock", js.printBlock) - debug.Set("dumpBlock", js.dumpBlock) - debug.Set("getBlockRlp", js.getBlockRlp) - debug.Set("setHead", js.setHead) - debug.Set("processBlock", js.debugBlock) - debug.Set("seedhash", js.seedHash) - debug.Set("insertBlock", js.insertBlockRlp) - // undocumented temporary - debug.Set("waitForBlocks", js.waitForBlocks) - - admin.Set("metrics", js.metrics) -} - -// generic helper to getBlock by Number/Height or Hex depending on autodetected input -// if argument is missing the current block is returned -// if block is not found or there is problem with decoding -// the appropriate value is returned and block is guaranteed to be nil -func (js *jsre) getBlock(call otto.FunctionCall) (*types.Block, error) { - var block *types.Block - if len(call.ArgumentList) > 0 { - if call.Argument(0).IsNumber() { - num, _ := call.Argument(0).ToInteger() - block = js.ethereum.ChainManager().GetBlockByNumber(uint64(num)) - } else if call.Argument(0).IsString() { - hash, _ := call.Argument(0).ToString() - block = js.ethereum.ChainManager().GetBlock(common.HexToHash(hash)) - } else { - return nil, errors.New("invalid argument for dump. Either hex string or number") - } - } else { - block = js.ethereum.ChainManager().CurrentBlock() - } - - if block == nil { - return nil, errors.New("block not found") - } - return block, nil -} - -func (js *jsre) seedHash(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) > 0 { - if call.Argument(0).IsNumber() { - num, _ := call.Argument(0).ToInteger() - hash, err := ethash.GetSeedHash(uint64(num)) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - v, _ := call.Otto.ToValue(fmt.Sprintf("0x%x", hash)) - return v - } else { - fmt.Println("arg not a number") - } - } else { - fmt.Println("requires number argument") - } - - return otto.UndefinedValue() -} - -func (js *jsre) allPendingTransactions(call otto.FunctionCall) otto.Value { - txs := js.ethereum.TxPool().GetTransactions() - - ltxs := make([]*tx, len(txs)) - for i, tx := range txs { - // no need to check err - ltxs[i] = newTx(tx) - } - - v, _ := call.Otto.ToValue(ltxs) - return v -} - -func (js *jsre) allQueuedTransactions(call otto.FunctionCall) otto.Value { - txs := js.ethereum.TxPool().GetQueuedTransactions() - - ltxs := make([]*tx, len(txs)) - for i, tx := range txs { - // no need to check err - ltxs[i] = newTx(tx) - } - - v, _ := call.Otto.ToValue(ltxs) - return v -} - -func (js *jsre) pendingTransactions(call otto.FunctionCall) otto.Value { - txs := js.ethereum.TxPool().GetTransactions() - - // grab the accounts from the account manager. This will help with determening which - // transactions should be returned. - accounts, err := js.ethereum.AccountManager().Accounts() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - // Add the accouns to a new set - accountSet := set.New() - for _, account := range accounts { - accountSet.Add(account.Address) - } - - //ltxs := make([]*tx, len(txs)) - var ltxs []*tx - for _, tx := range txs { - if from, _ := tx.From(); accountSet.Has(from) { - ltxs = append(ltxs, newTx(tx)) - } - } - - v, _ := call.Otto.ToValue(ltxs) - return v -} - -func (js *jsre) resend(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) == 0 { - fmt.Println("first argument must be a transaction") - return otto.FalseValue() - } - - v, err := call.Argument(0).Export() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - if tx, ok := v.(*tx); ok { - gl, gp := tx.GasLimit, tx.GasPrice - if len(call.ArgumentList) > 1 { - gp = call.Argument(1).String() - } - if len(call.ArgumentList) > 2 { - gl = call.Argument(2).String() - } - - ret, err := js.xeth.Transact(tx.From, tx.To, tx.Nonce, tx.Value, gl, gp, tx.Data) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - js.ethereum.TxPool().RemoveTransactions(types.Transactions{tx.tx}) - - v, _ := call.Otto.ToValue(ret) - return v - } - - fmt.Println("first argument must be a transaction") - return otto.FalseValue() -} - -func (js *jsre) sign(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) != 2 { - fmt.Println("requires 2 arguments: eth.sign(signer, data)") - return otto.UndefinedValue() - } - signer, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - data, err := call.Argument(1).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - signed, err := js.xeth.Sign(signer, data, false) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - v, _ := call.Otto.ToValue(signed) - return v -} - -func (js *jsre) debugBlock(call otto.FunctionCall) otto.Value { - block, err := js.getBlock(call) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - tstart := time.Now() - old := vm.Debug - - if len(call.ArgumentList) > 1 { - vm.Debug, _ = call.Argument(1).ToBoolean() - } - - _, err = js.ethereum.BlockProcessor().RetryProcess(block) - if err != nil { - fmt.Println(err) - r, _ := call.Otto.ToValue(map[string]interface{}{"success": false, "time": time.Since(tstart).Seconds()}) - return r - } - vm.Debug = old - - r, _ := call.Otto.ToValue(map[string]interface{}{"success": true, "time": time.Since(tstart).Seconds()}) - return r -} - -func (js *jsre) insertBlockRlp(call otto.FunctionCall) otto.Value { - tstart := time.Now() - - var block types.Block - if call.Argument(0).IsString() { - blockRlp, _ := call.Argument(0).ToString() - err := rlp.DecodeBytes(common.Hex2Bytes(blockRlp), &block) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - } - - old := vm.Debug - vm.Debug = true - _, err := js.ethereum.BlockProcessor().RetryProcess(&block) - if err != nil { - fmt.Println(err) - r, _ := call.Otto.ToValue(map[string]interface{}{"success": false, "time": time.Since(tstart).Seconds()}) - return r - } - vm.Debug = old - - r, _ := call.Otto.ToValue(map[string]interface{}{"success": true, "time": time.Since(tstart).Seconds()}) - return r -} - -func (js *jsre) setHead(call otto.FunctionCall) otto.Value { - block, err := js.getBlock(call) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - js.ethereum.ChainManager().SetHead(block) - return otto.UndefinedValue() -} - -func (js *jsre) syncProgress(call otto.FunctionCall) otto.Value { - pending, cached, importing, eta := js.ethereum.Downloader().Stats() - v, _ := call.Otto.ToValue(map[string]interface{}{ - "pending": pending, - "cached": cached, - "importing": importing, - "estimate": (eta / time.Second * time.Second).String(), - }) - return v -} - -func (js *jsre) getBlockRlp(call otto.FunctionCall) otto.Value { - block, err := js.getBlock(call) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - encoded, _ := rlp.EncodeToBytes(block) - v, _ := call.Otto.ToValue(fmt.Sprintf("%x", encoded)) - return v -} - -func (js *jsre) setExtra(call otto.FunctionCall) otto.Value { - extra, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - if len(extra) > 1024 { - fmt.Println("error: cannot exceed 1024 bytes") - return otto.UndefinedValue() - } - - js.ethereum.Miner().SetExtra([]byte(extra)) - return otto.UndefinedValue() -} - -func (js *jsre) setGasPrice(call otto.FunctionCall) otto.Value { - gasPrice, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - js.ethereum.Miner().SetGasPrice(common.String2Big(gasPrice)) - return otto.UndefinedValue() -} - -func (js *jsre) hashrate(call otto.FunctionCall) otto.Value { - v, _ := call.Otto.ToValue(js.ethereum.Miner().HashRate()) - return v -} - -func (js *jsre) makeDAG(call otto.FunctionCall) otto.Value { - blockNumber, err := call.Argument(1).ToInteger() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - err = ethash.MakeDAG(uint64(blockNumber), "") - if err != nil { - return otto.FalseValue() - } - return otto.TrueValue() -} - -func (js *jsre) startAutoDAG(otto.FunctionCall) otto.Value { - js.ethereum.StartAutoDAG() - return otto.TrueValue() -} - -func (js *jsre) stopAutoDAG(otto.FunctionCall) otto.Value { - js.ethereum.StopAutoDAG() - return otto.TrueValue() -} - -func (js *jsre) backtrace(call otto.FunctionCall) otto.Value { - tracestr, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - glog.GetTraceLocation().Set(tracestr) - - return otto.UndefinedValue() -} - -func (js *jsre) verbosity(call otto.FunctionCall) otto.Value { - v, err := call.Argument(0).ToInteger() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - glog.SetV(int(v)) - return otto.UndefinedValue() -} - -func (js *jsre) startMining(call otto.FunctionCall) otto.Value { - var ( - threads int64 - err error - ) - - if len(call.ArgumentList) > 0 { - threads, err = call.Argument(0).ToInteger() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - } else { - threads = int64(js.ethereum.MinerThreads) - } - - // switch on DAG autogeneration when miner starts - js.ethereum.StartAutoDAG() - - err = js.ethereum.StartMining(int(threads)) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - return otto.TrueValue() -} - -func (js *jsre) stopMining(call otto.FunctionCall) otto.Value { - js.ethereum.StopMining() - js.ethereum.StopAutoDAG() - return otto.TrueValue() -} - -func (js *jsre) startRPC(call otto.FunctionCall) otto.Value { - addr, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - port, err := call.Argument(1).ToInteger() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - corsDomain := js.corsDomain - if len(call.ArgumentList) > 2 { - corsDomain, err = call.Argument(2).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - } - - config := rpc.RpcConfig{ - ListenAddress: addr, - ListenPort: uint(port), - CorsDomain: corsDomain, - } - - xeth := xeth.New(js.ethereum, nil) - err = rpc.Start(xeth, config) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - return otto.TrueValue() -} - -func (js *jsre) stopRPC(call otto.FunctionCall) otto.Value { - if rpc.Stop() == nil { - return otto.TrueValue() - } - return otto.FalseValue() -} - -func (js *jsre) addPeer(call otto.FunctionCall) otto.Value { - nodeURL, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - err = js.ethereum.AddPeer(nodeURL) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - return otto.TrueValue() -} - -func (js *jsre) unlock(call otto.FunctionCall) otto.Value { - addr, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - seconds, err := call.Argument(2).ToInteger() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - if seconds == 0 { - seconds = accounts.DefaultAccountUnlockDuration - } - - arg := call.Argument(1) - var passphrase string - if arg.IsUndefined() { - fmt.Println("Please enter a passphrase now.") - passphrase, err = utils.PromptPassword("Passphrase: ", true) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - } else { - passphrase, err = arg.ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - } - am := js.ethereum.AccountManager() - err = am.TimedUnlock(common.HexToAddress(addr), passphrase, time.Duration(seconds)*time.Second) - if err != nil { - fmt.Printf("Unlock account failed '%v'\n", err) - return otto.FalseValue() - } - return otto.TrueValue() -} - -func (js *jsre) newAccount(call otto.FunctionCall) otto.Value { - arg := call.Argument(0) - var passphrase string - if arg.IsUndefined() { - fmt.Println("The new account will be encrypted with a passphrase.") - fmt.Println("Please enter a passphrase now.") - auth, err := utils.PromptPassword("Passphrase: ", true) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - confirm, err := utils.PromptPassword("Repeat Passphrase: ", false) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - if auth != confirm { - fmt.Println("Passphrases did not match.") - return otto.FalseValue() - } - passphrase = auth - } else { - var err error - passphrase, err = arg.ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - } - acct, err := js.ethereum.AccountManager().NewAccount(passphrase) - if err != nil { - fmt.Printf("Could not create the account: %v", err) - return otto.UndefinedValue() - } - v, _ := call.Otto.ToValue(acct.Address.Hex()) - return v -} - -func (js *jsre) nodeInfo(call otto.FunctionCall) otto.Value { - v, _ := call.Otto.ToValue(js.ethereum.NodeInfo()) - return v -} - -func (js *jsre) peers(call otto.FunctionCall) otto.Value { - v, _ := call.Otto.ToValue(js.ethereum.PeersInfo()) - return v -} - -func (js *jsre) importChain(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) == 0 { - fmt.Println("require file name. admin.importChain(filename)") - return otto.FalseValue() - } - fn, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - if err := utils.ImportChain(js.ethereum.ChainManager(), fn); err != nil { - fmt.Println("Import error: ", err) - return otto.FalseValue() - } - return otto.TrueValue() -} - -func (js *jsre) exportChain(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) == 0 { - fmt.Println("require file name: admin.exportChain(filename)") - return otto.FalseValue() - } - - fn, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - if err := utils.ExportChain(js.ethereum.ChainManager(), fn); err != nil { - fmt.Println(err) - return otto.FalseValue() - } - return otto.TrueValue() -} - -func (js *jsre) printBlock(call otto.FunctionCall) otto.Value { - block, err := js.getBlock(call) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - fmt.Println(block) - - return otto.UndefinedValue() -} - -func (js *jsre) dumpBlock(call otto.FunctionCall) otto.Value { - block, err := js.getBlock(call) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - statedb := state.New(block.Root(), js.ethereum.StateDb()) - dump := statedb.RawDump() - v, _ := call.Otto.ToValue(dump) - return v -} - -func (js *jsre) waitForBlocks(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) > 2 { - fmt.Println("requires 0, 1 or 2 arguments: admin.debug.waitForBlock(minHeight, timeout)") - return otto.FalseValue() - } - var n, timeout int64 - var timer <-chan time.Time - var height *big.Int - var err error - args := len(call.ArgumentList) - if args == 2 { - timeout, err = call.Argument(1).ToInteger() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - timer = time.NewTimer(time.Duration(timeout) * time.Second).C - } - if args >= 1 { - n, err = call.Argument(0).ToInteger() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - height = big.NewInt(n) - } - - if args == 0 { - height = js.xeth.CurrentBlock().Number() - height.Add(height, common.Big1) - } - - wait := js.wait - js.wait <- height - select { - case <-timer: - // if times out make sure the xeth loop does not block - go func() { - select { - case wait <- nil: - case <-wait: - } - }() - return otto.UndefinedValue() - case height = <-wait: - } - v, _ := call.Otto.ToValue(height.Uint64()) - return v -} - -func (js *jsre) metrics(call otto.FunctionCall) otto.Value { - // Create a rate formatter - units := []string{"", "K", "M", "G", "T", "E", "P"} - round := func(value float64, prec int) string { - unit := 0 - for value >= 1000 { - unit, value, prec = unit+1, value/1000, 2 - } - return fmt.Sprintf(fmt.Sprintf("%%.%df%s", prec, units[unit]), value) - } - format := func(total float64, rate float64) string { - return fmt.Sprintf("%s (%s/s)", round(total, 0), round(rate, 2)) - } - // Iterate over all the metrics, and just dump for now - counters := make(map[string]interface{}) - metrics.DefaultRegistry.Each(func(name string, metric interface{}) { - // Create or retrieve the counter hierarchy for this metric - root, parts := counters, strings.Split(name, "/") - for _, part := range parts[:len(parts)-1] { - if _, ok := root[part]; !ok { - root[part] = make(map[string]interface{}) - } - root = root[part].(map[string]interface{}) - } - name = parts[len(parts)-1] - - // Fill the counter with the metric details - switch metric := metric.(type) { - case metrics.Meter: - root[name] = map[string]interface{}{ - "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), - "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), - "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), - "Total": format(float64(metric.Count()), metric.RateMean()), - } - - case metrics.Timer: - root[name] = map[string]interface{}{ - "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), - "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), - "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), - "Count": format(float64(metric.Count()), metric.RateMean()), - "Maximum": time.Duration(metric.Max()).String(), - "Minimum": time.Duration(metric.Min()).String(), - "Percentile": map[string]interface{}{ - "20": time.Duration(metric.Percentile(0.2)).String(), - "50": time.Duration(metric.Percentile(0.5)).String(), - "80": time.Duration(metric.Percentile(0.8)).String(), - "95": time.Duration(metric.Percentile(0.95)).String(), - "99": time.Duration(metric.Percentile(0.99)).String(), - }, - } - - default: - root[name] = "Unknown metric type" - } - }) - // Flatten the counters into some metrics and return - v, _ := call.Otto.ToValue(counters) - return v -} - -func (js *jsre) sleep(call otto.FunctionCall) otto.Value { - sec, err := call.Argument(0).ToInteger() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - time.Sleep(time.Duration(sec) * time.Second) - return otto.UndefinedValue() -} - -func (js *jsre) setSolc(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) != 1 { - fmt.Println("needs 1 argument: admin.contractInfo.setSolc(solcPath)") - return otto.FalseValue() - } - solcPath, err := call.Argument(0).ToString() - if err != nil { - return otto.FalseValue() - } - solc, err := js.xeth.SetSolc(solcPath) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - fmt.Println(solc.Info()) - return otto.TrueValue() -} - -func (js *jsre) register(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) != 4 { - fmt.Println("requires 4 arguments: admin.contractInfo.register(fromaddress, contractaddress, contract, filename)") - return otto.UndefinedValue() - } - sender, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - address, err := call.Argument(1).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - raw, err := call.Argument(2).Export() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - jsonraw, err := json.Marshal(raw) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - var contract compiler.Contract - err = json.Unmarshal(jsonraw, &contract) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - filename, err := call.Argument(3).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - contenthash, err := compiler.ExtractInfo(&contract, filename) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - // sender and contract address are passed as hex strings - codeb := js.xeth.CodeAtBytes(address) - codehash := common.BytesToHash(crypto.Sha3(codeb)) - - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - registry := resolver.New(js.xeth) - - _, err = registry.RegisterContentHash(common.HexToAddress(sender), codehash, contenthash) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - v, _ := call.Otto.ToValue(contenthash.Hex()) - return v -} - -func (js *jsre) registerUrl(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) != 3 { - fmt.Println("requires 3 arguments: admin.contractInfo.register(fromaddress, contenthash, filename)") - return otto.FalseValue() - } - sender, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - contenthash, err := call.Argument(1).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - url, err := call.Argument(2).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - registry := resolver.New(js.xeth) - - _, err = registry.RegisterUrl(common.HexToAddress(sender), common.HexToHash(contenthash), url) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - return otto.TrueValue() -} - -func (js *jsre) getContractInfo(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) != 1 { - fmt.Println("requires 1 argument: admin.contractInfo.register(contractaddress)") - return otto.FalseValue() - } - addr, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - infoDoc, err := natspec.FetchDocsForContract(addr, js.xeth, ds) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - var info compiler.ContractInfo - err = json.Unmarshal(infoDoc, &info) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - v, _ := call.Otto.ToValue(info) - return v -} - -func (js *jsre) startNatSpec(call otto.FunctionCall) otto.Value { - js.ethereum.NatSpec = true - return otto.TrueValue() -} - -func (js *jsre) stopNatSpec(call otto.FunctionCall) otto.Value { - js.ethereum.NatSpec = false - return otto.TrueValue() -} - -func (js *jsre) newRegistry(call otto.FunctionCall) otto.Value { - - if len(call.ArgumentList) != 1 { - fmt.Println("requires 1 argument: admin.contractInfo.newRegistry(adminaddress)") - return otto.FalseValue() - } - addr, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - registry := resolver.New(js.xeth) - err = registry.CreateContracts(common.HexToAddress(addr)) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - return otto.TrueValue() -} - -// internal transaction type which will allow us to resend transactions using `eth.resend` -type tx struct { - tx *types.Transaction - - To string - From string - Nonce string - Value string - Data string - GasLimit string - GasPrice string -} - -func newTx(t *types.Transaction) *tx { - from, _ := t.From() - var to string - if t := t.To(); t != nil { - to = t.Hex() - } - - return &tx{ - tx: t, - To: to, - From: from.Hex(), - Value: t.Amount.String(), - Nonce: strconv.Itoa(int(t.Nonce())), - Data: "0x" + common.Bytes2Hex(t.Data()), - GasLimit: t.GasLimit.String(), - GasPrice: t.GasPrice().String(), - } -} -- cgit v1.2.3 From e5b820c47b9343d3801e1ebbeb4a8f40843ea87c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 24 Jun 2015 14:38:58 +0300 Subject: cmd/geth, rpc/api: extend metrics API, add a basic monitor command --- cmd/geth/main.go | 10 +++ cmd/geth/monitorcmd.go | 180 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 cmd/geth/monitorcmd.go (limited to 'cmd') diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 963aced15..f7b4810cd 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -214,6 +214,16 @@ The Geth console is an interactive shell for the JavaScript runtime environment which exposes a node admin interface as well as the Ðapp JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console. This command allows to open a console on a running geth node. +`, + }, + { + Action: monitor, + Name: "monitor", + Usage: `Geth Monitor: node metrics monitoring and visualization`, + Description: ` +The Geth monitor is a tool to collect and visualize various internal metrics +gathered by the node, supporting different chart types as well as the capacity +to display multiple metrics simultaneously. `, }, { diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go new file mode 100644 index 000000000..2989800f9 --- /dev/null +++ b/cmd/geth/monitorcmd.go @@ -0,0 +1,180 @@ +package main + +import ( + "reflect" + "sort" + "strings" + "time" + + "github.com/codegangsta/cli" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/comms" + "github.com/gizak/termui" +) + +// monitor starts a terminal UI based monitoring tool for the requested metrics. +func monitor(ctx *cli.Context) { + var ( + client comms.EthereumClient + args []string + err error + ) + // Attach to an Ethereum node over IPC or RPC + if ctx.Args().Present() { + // Try to interpret the first parameter as an endpoint + client, err = comms.ClientFromEndpoint(ctx.Args().First(), codec.JSON) + if err == nil { + args = ctx.Args().Tail() + } + } + if !ctx.Args().Present() || err != nil { + // Either no args were given, or not endpoint, use defaults + cfg := comms.IpcConfig{ + Endpoint: ctx.GlobalString(utils.IPCPathFlag.Name), + } + args = ctx.Args() + client, err = comms.NewIpcClient(cfg, codec.JSON) + } + if err != nil { + utils.Fatalf("Unable to attach to geth node - %v", err) + } + defer client.Close() + + xeth := rpc.NewXeth(client) + + // Retrieve all the available metrics and resolve the user pattens + metrics, err := xeth.Call("debug_metrics", []interface{}{true}) + if err != nil { + utils.Fatalf("Failed to retrieve system metrics: %v", err) + } + monitored := resolveMetrics(metrics, args) + sort.Strings(monitored) + + // Create the access function and check that the metric exists + value := func(metrics map[string]interface{}, metric string) float64 { + parts, found := strings.Split(metric, "/"), true + for _, part := range parts[:len(parts)-1] { + metrics, found = metrics[part].(map[string]interface{}) + if !found { + utils.Fatalf("Metric not found: %s", metric) + } + } + if v, ok := metrics[parts[len(parts)-1]].(float64); ok { + return v + } + utils.Fatalf("Metric not float64: %s", metric) + return 0 + } + // Assemble the terminal UI + if err := termui.Init(); err != nil { + utils.Fatalf("Unable to initialize terminal UI: %v", err) + } + defer termui.Close() + + termui.UseTheme("helloworld") + + charts := make([]*termui.LineChart, len(monitored)) + for i, metric := range monitored { + charts[i] = termui.NewLineChart() + charts[i].Border.Label = metric + charts[i].Data = make([]float64, 512) + charts[i].DataLabels = []string{""} + charts[i].Height = termui.TermHeight() / len(monitored) + charts[i].AxesColor = termui.ColorWhite + charts[i].LineColor = termui.ColorGreen + + termui.Body.AddRows(termui.NewRow(termui.NewCol(12, 0, charts[i]))) + } + termui.Body.Align() + termui.Render(termui.Body) + + refresh := time.Tick(time.Second) + for { + select { + case event := <-termui.EventCh(): + if event.Type == termui.EventKey && event.Ch == 'q' { + return + } + if event.Type == termui.EventResize { + termui.Body.Width = termui.TermWidth() + for _, chart := range charts { + chart.Height = termui.TermHeight() / len(monitored) + } + termui.Body.Align() + termui.Render(termui.Body) + } + case <-refresh: + metrics, err := xeth.Call("debug_metrics", []interface{}{true}) + if err != nil { + utils.Fatalf("Failed to retrieve system metrics: %v", err) + } + for i, metric := range monitored { + charts[i].Data = append([]float64{value(metrics, metric)}, charts[i].Data[:len(charts[i].Data)-1]...) + } + termui.Render(termui.Body) + } + } +} + +// resolveMetrics takes a list of input metric patterns, and resolves each to one +// or more canonical metric names. +func resolveMetrics(metrics map[string]interface{}, patterns []string) []string { + res := []string{} + for _, pattern := range patterns { + res = append(res, resolveMetric(metrics, pattern, "")...) + } + return res +} + +// resolveMetrics takes a single of input metric pattern, and resolves it to one +// or more canonical metric names. +func resolveMetric(metrics map[string]interface{}, pattern string, path string) []string { + var ok bool + + // Build up the canonical metric path + parts := strings.Split(pattern, "/") + for len(parts) > 1 { + if metrics, ok = metrics[parts[0]].(map[string]interface{}); !ok { + utils.Fatalf("Failed to retrieve system metrics: %s", path+parts[0]) + } + path += parts[0] + "/" + parts = parts[1:] + } + // Depending what the last link is, return or expand + switch metric := metrics[parts[0]].(type) { + case float64: + // Final metric value found, return as singleton + return []string{path + parts[0]} + + case map[string]interface{}: + return expandMetrics(metric, path+parts[0]+"/") + + default: + utils.Fatalf("Metric pattern resolved to unexpected type: %v", reflect.TypeOf(metric)) + return nil + } +} + +// expandMetrics expands the entire tree of metrics into a flat list of paths. +func expandMetrics(metrics map[string]interface{}, path string) []string { + // Iterate over all fields and expand individually + list := []string{} + for name, metric := range metrics { + switch metric := metric.(type) { + case float64: + // Final metric value found, append to list + list = append(list, path+name) + + case map[string]interface{}: + // Tree of metrics found, expand recursively + list = append(list, expandMetrics(metric, path+name+"/")...) + + default: + utils.Fatalf("Metric pattern %s resolved to unexpected type: %v", path+name, reflect.TypeOf(metric)) + return nil + } + } + return list +} -- cgit v1.2.3 From bf99d5b33c716ecb8b7dac8234df07b0ea82c48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 24 Jun 2015 16:39:22 +0300 Subject: cmd/geth: polish the monitoring charts a bit --- cmd/geth/monitorcmd.go | 57 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 6 deletions(-) (limited to 'cmd') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 2989800f9..06ccf90be 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -1,6 +1,7 @@ package main import ( + "math" "reflect" "sort" "strings" @@ -67,7 +68,7 @@ func monitor(ctx *cli.Context) { utils.Fatalf("Metric not float64: %s", metric) return 0 } - // Assemble the terminal UI + // Create and configure the chart UI defaults if err := termui.Init(); err != nil { utils.Fatalf("Unable to initialize terminal UI: %v", err) } @@ -75,17 +76,33 @@ func monitor(ctx *cli.Context) { termui.UseTheme("helloworld") + rows := 5 + cols := (len(monitored) + rows - 1) / rows + for i := 0; i < rows; i++ { + termui.Body.AddRows(termui.NewRow()) + } + // Create each individual data chart charts := make([]*termui.LineChart, len(monitored)) + data := make([][]float64, len(monitored)) + for i := 0; i < len(data); i++ { + data[i] = make([]float64, 512) + } for i, metric := range monitored { charts[i] = termui.NewLineChart() - charts[i].Border.Label = metric + charts[i].Data = make([]float64, 512) charts[i].DataLabels = []string{""} - charts[i].Height = termui.TermHeight() / len(monitored) + charts[i].Height = termui.TermHeight() / rows charts[i].AxesColor = termui.ColorWhite charts[i].LineColor = termui.ColorGreen + charts[i].PaddingBottom = -1 - termui.Body.AddRows(termui.NewRow(termui.NewCol(12, 0, charts[i]))) + charts[i].Border.Label = metric + charts[i].Border.LabelFgColor = charts[i].Border.FgColor + charts[i].Border.FgColor = charts[i].Border.BgColor + + row := termui.Body.Rows[i%rows] + row.Cols = append(row.Cols, termui.NewCol(12/cols, 0, charts[i])) } termui.Body.Align() termui.Render(termui.Body) @@ -100,7 +117,7 @@ func monitor(ctx *cli.Context) { if event.Type == termui.EventResize { termui.Body.Width = termui.TermWidth() for _, chart := range charts { - chart.Height = termui.TermHeight() / len(monitored) + chart.Height = termui.TermHeight() / rows } termui.Body.Align() termui.Render(termui.Body) @@ -111,7 +128,8 @@ func monitor(ctx *cli.Context) { utils.Fatalf("Failed to retrieve system metrics: %v", err) } for i, metric := range monitored { - charts[i].Data = append([]float64{value(metrics, metric)}, charts[i].Data[:len(charts[i].Data)-1]...) + data[i] = append([]float64{value(metrics, metric)}, data[i][:len(data[i])-1]...) + updateChart(metric, data[i], charts[i]) } termui.Render(termui.Body) } @@ -178,3 +196,30 @@ func expandMetrics(metrics map[string]interface{}, path string) []string { } return list } + +// updateChart inserts a dataset into a line chart, scaling appropriately as to +// not display weird labels, also updating the chart label accordingly. +func updateChart(metric string, data []float64, chart *termui.LineChart) { + units := []string{"", "K", "M", "G", "T", "E", "P"} + colors := []termui.Attribute{termui.ColorBlue, termui.ColorCyan, termui.ColorGreen, termui.ColorYellow, termui.ColorRed, termui.ColorRed, termui.ColorRed} + + // Find the maximum value and scale under 1K + high := data[0] + for _, value := range data[1:] { + high = math.Max(high, value) + } + unit, scale := 0, 1.0 + for high >= 1000 { + high, unit, scale = high/1000, unit+1, scale*1000 + } + // Update the chart's data points with the scaled values + for i, value := range data { + chart.Data[i] = value / scale + } + // Update the chart's label with the scale units + chart.Border.Label = metric + if unit > 0 { + chart.Border.Label += " [" + units[unit] + "]" + } + chart.LineColor = colors[unit] +} -- cgit v1.2.3 From 302187ae397c5e06a9f086183d55f591f5daf588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 24 Jun 2015 17:12:38 +0300 Subject: cmd/geth: allow branching metric patterns --- cmd/geth/monitorcmd.go | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) (limited to 'cmd') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 06ccf90be..ec0dfb8f2 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -149,30 +149,37 @@ func resolveMetrics(metrics map[string]interface{}, patterns []string) []string // resolveMetrics takes a single of input metric pattern, and resolves it to one // or more canonical metric names. func resolveMetric(metrics map[string]interface{}, pattern string, path string) []string { - var ok bool - - // Build up the canonical metric path - parts := strings.Split(pattern, "/") - for len(parts) > 1 { - if metrics, ok = metrics[parts[0]].(map[string]interface{}); !ok { - utils.Fatalf("Failed to retrieve system metrics: %s", path+parts[0]) + results := []string{} + + // If a nested metric was requested, recurse optionally branching (via comma) + parts := strings.SplitN(pattern, "/", 2) + if len(parts) > 1 { + for _, variation := range strings.Split(parts[0], ",") { + if submetrics, ok := metrics[variation].(map[string]interface{}); !ok { + utils.Fatalf("Failed to retrieve system metrics: %s", path+variation) + return nil + } else { + results = append(results, resolveMetric(submetrics, parts[1], path+variation+"/")...) + } } - path += parts[0] + "/" - parts = parts[1:] + return results } // Depending what the last link is, return or expand - switch metric := metrics[parts[0]].(type) { - case float64: - // Final metric value found, return as singleton - return []string{path + parts[0]} + for _, variation := range strings.Split(pattern, ",") { + switch metric := metrics[variation].(type) { + case float64: + // Final metric value found, return as singleton + results = append(results, path+variation) - case map[string]interface{}: - return expandMetrics(metric, path+parts[0]+"/") + case map[string]interface{}: + results = append(results, expandMetrics(metric, path+variation+"/")...) - default: - utils.Fatalf("Metric pattern resolved to unexpected type: %v", reflect.TypeOf(metric)) - return nil + default: + utils.Fatalf("Metric pattern resolved to unexpected type: %v", reflect.TypeOf(metric)) + return nil + } } + return results } // expandMetrics expands the entire tree of metrics into a flat list of paths. -- cgit v1.2.3 From 92ef33d97a437dce2d7b55f06342de388d95f575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 24 Jun 2015 18:30:00 +0300 Subject: rpc/api, cmd/geth: retrievel all percentiles, add time units --- cmd/geth/monitorcmd.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'cmd') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index ec0dfb8f2..53eb61a46 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -76,7 +76,10 @@ func monitor(ctx *cli.Context) { termui.UseTheme("helloworld") - rows := 5 + rows := len(monitored) + if rows > 5 { + rows = 5 + } cols := (len(monitored) + rows - 1) / rows for i := 0; i < rows; i++ { termui.Body.AddRows(termui.NewRow()) @@ -207,8 +210,9 @@ func expandMetrics(metrics map[string]interface{}, path string) []string { // updateChart inserts a dataset into a line chart, scaling appropriately as to // not display weird labels, also updating the chart label accordingly. func updateChart(metric string, data []float64, chart *termui.LineChart) { - units := []string{"", "K", "M", "G", "T", "E", "P"} - colors := []termui.Attribute{termui.ColorBlue, termui.ColorCyan, termui.ColorGreen, termui.ColorYellow, termui.ColorRed, termui.ColorRed, termui.ColorRed} + dataUnits := []string{"", "K", "M", "G", "T", "E"} + timeUnits := []string{"ns", "µs", "ms", "s", "ks", "ms"} + colors := []termui.Attribute{termui.ColorBlue, termui.ColorCyan, termui.ColorGreen, termui.ColorYellow, termui.ColorRed, termui.ColorRed} // Find the maximum value and scale under 1K high := data[0] @@ -225,7 +229,12 @@ func updateChart(metric string, data []float64, chart *termui.LineChart) { } // Update the chart's label with the scale units chart.Border.Label = metric - if unit > 0 { + + units := dataUnits + if strings.Contains(metric, "Percentiles") { + units = timeUnits + } + if len(units[unit]) > 0 { chart.Border.Label += " [" + units[unit] + "]" } chart.LineColor = colors[unit] -- cgit v1.2.3 From b98b444179ed16d54fab1ff416cf561427151f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Jun 2015 10:36:47 +0300 Subject: cmd/geth: add attach and rows flags to the monitor command --- cmd/geth/main.go | 11 +-------- cmd/geth/monitorcmd.go | 65 ++++++++++++++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 33 deletions(-) (limited to 'cmd') diff --git a/cmd/geth/main.go b/cmd/geth/main.go index f7b4810cd..f1c229d1f 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -72,6 +72,7 @@ func init() { upgradedbCommand, removedbCommand, dumpCommand, + monitorCommand, { Action: makedag, Name: "makedag", @@ -214,16 +215,6 @@ The Geth console is an interactive shell for the JavaScript runtime environment which exposes a node admin interface as well as the Ðapp JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console. This command allows to open a console on a running geth node. -`, - }, - { - Action: monitor, - Name: "monitor", - Usage: `Geth Monitor: node metrics monitoring and visualization`, - Description: ` -The Geth monitor is a tool to collect and visualize various internal metrics -gathered by the node, supporting different chart types as well as the capacity -to display multiple metrics simultaneously. `, }, { diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 53eb61a46..7b0076860 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -9,48 +9,61 @@ import ( "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/comms" "github.com/gizak/termui" ) +var ( + monitorCommandAttachFlag = cli.StringFlag{ + Name: "attach", + Value: "ipc:" + common.DefaultIpcPath(), + Usage: "IPC or RPC API endpoint to attach to", + } + monitorCommandRowsFlag = cli.IntFlag{ + Name: "rows", + Value: 5, + Usage: "Rows (maximum) to display the charts in", + } + monitorCommand = cli.Command{ + Action: monitor, + Name: "monitor", + Usage: `Geth Monitor: node metrics monitoring and visualization`, + Description: ` +The Geth monitor is a tool to collect and visualize various internal metrics +gathered by the node, supporting different chart types as well as the capacity +to display multiple metrics simultaneously. +`, + Flags: []cli.Flag{ + monitorCommandAttachFlag, + monitorCommandRowsFlag, + }, + } +) + // monitor starts a terminal UI based monitoring tool for the requested metrics. func monitor(ctx *cli.Context) { var ( client comms.EthereumClient - args []string err error ) // Attach to an Ethereum node over IPC or RPC - if ctx.Args().Present() { - // Try to interpret the first parameter as an endpoint - client, err = comms.ClientFromEndpoint(ctx.Args().First(), codec.JSON) - if err == nil { - args = ctx.Args().Tail() - } - } - if !ctx.Args().Present() || err != nil { - // Either no args were given, or not endpoint, use defaults - cfg := comms.IpcConfig{ - Endpoint: ctx.GlobalString(utils.IPCPathFlag.Name), - } - args = ctx.Args() - client, err = comms.NewIpcClient(cfg, codec.JSON) - } - if err != nil { - utils.Fatalf("Unable to attach to geth node - %v", err) + endpoint := ctx.String(monitorCommandAttachFlag.Name) + if client, err = comms.ClientFromEndpoint(endpoint, codec.JSON); err != nil { + utils.Fatalf("Unable to attach to geth node: %v", err) } defer client.Close() xeth := rpc.NewXeth(client) // Retrieve all the available metrics and resolve the user pattens - metrics, err := xeth.Call("debug_metrics", []interface{}{true}) + metrics, err := retrieveMetrics(xeth) if err != nil { utils.Fatalf("Failed to retrieve system metrics: %v", err) } - monitored := resolveMetrics(metrics, args) + monitored := resolveMetrics(metrics, ctx.Args()) sort.Strings(monitored) // Create the access function and check that the metric exists @@ -77,8 +90,8 @@ func monitor(ctx *cli.Context) { termui.UseTheme("helloworld") rows := len(monitored) - if rows > 5 { - rows = 5 + if max := ctx.Int(monitorCommandRowsFlag.Name); rows > max { + rows = max } cols := (len(monitored) + rows - 1) / rows for i := 0; i < rows; i++ { @@ -126,7 +139,7 @@ func monitor(ctx *cli.Context) { termui.Render(termui.Body) } case <-refresh: - metrics, err := xeth.Call("debug_metrics", []interface{}{true}) + metrics, err := retrieveMetrics(xeth) if err != nil { utils.Fatalf("Failed to retrieve system metrics: %v", err) } @@ -139,6 +152,12 @@ func monitor(ctx *cli.Context) { } } +// retrieveMetrics contacts the attached geth node and retrieves the entire set +// of collected system metrics. +func retrieveMetrics(xeth *rpc.Xeth) (map[string]interface{}, error) { + return xeth.Call("debug_metrics", []interface{}{true}) +} + // resolveMetrics takes a list of input metric patterns, and resolves each to one // or more canonical metric names. func resolveMetrics(metrics map[string]interface{}, patterns []string) []string { -- cgit v1.2.3 From d02f07a983ef4b42bfda86bd46c200bb4104e922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Jun 2015 11:32:21 +0300 Subject: cmd/geth: polish monitor visuals, add footer, refresh flag --- cmd/geth/monitorcmd.go | 104 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 33 deletions(-) (limited to 'cmd') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 7b0076860..492865d0e 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "math" "reflect" "sort" @@ -20,12 +21,17 @@ var ( monitorCommandAttachFlag = cli.StringFlag{ Name: "attach", Value: "ipc:" + common.DefaultIpcPath(), - Usage: "IPC or RPC API endpoint to attach to", + Usage: "API endpoint to attach to", } monitorCommandRowsFlag = cli.IntFlag{ Name: "rows", Value: 5, - Usage: "Rows (maximum) to display the charts in", + Usage: "Maximum rows in the chart grid", + } + monitorCommandRefreshFlag = cli.IntFlag{ + Name: "refresh", + Value: 3, + Usage: "Refresh interval in seconds", } monitorCommand = cli.Command{ Action: monitor, @@ -39,6 +45,7 @@ to display multiple metrics simultaneously. Flags: []cli.Flag{ monitorCommandAttachFlag, monitorCommandRowsFlag, + monitorCommandRefreshFlag, }, } ) @@ -66,21 +73,6 @@ func monitor(ctx *cli.Context) { monitored := resolveMetrics(metrics, ctx.Args()) sort.Strings(monitored) - // Create the access function and check that the metric exists - value := func(metrics map[string]interface{}, metric string) float64 { - parts, found := strings.Split(metric, "/"), true - for _, part := range parts[:len(parts)-1] { - metrics, found = metrics[part].(map[string]interface{}) - if !found { - utils.Fatalf("Metric not found: %s", metric) - } - } - if v, ok := metrics[parts[len(parts)-1]].(float64); ok { - return v - } - utils.Fatalf("Metric not float64: %s", metric) - return 0 - } // Create and configure the chart UI defaults if err := termui.Init(); err != nil { utils.Fatalf("Unable to initialize terminal UI: %v", err) @@ -98,6 +90,10 @@ func monitor(ctx *cli.Context) { termui.Body.AddRows(termui.NewRow()) } // Create each individual data chart + footer := termui.NewPar("") + footer.HasBorder = true + footer.Height = 3 + charts := make([]*termui.LineChart, len(monitored)) data := make([][]float64, len(monitored)) for i := 0; i < len(data); i++ { @@ -105,25 +101,28 @@ func monitor(ctx *cli.Context) { } for i, metric := range monitored { charts[i] = termui.NewLineChart() - charts[i].Data = make([]float64, 512) charts[i].DataLabels = []string{""} - charts[i].Height = termui.TermHeight() / rows + charts[i].Height = (termui.TermHeight() - footer.Height) / rows charts[i].AxesColor = termui.ColorWhite - charts[i].LineColor = termui.ColorGreen charts[i].PaddingBottom = -1 charts[i].Border.Label = metric - charts[i].Border.LabelFgColor = charts[i].Border.FgColor + charts[i].Border.LabelFgColor = charts[i].Border.FgColor | termui.AttrBold charts[i].Border.FgColor = charts[i].Border.BgColor row := termui.Body.Rows[i%rows] row.Cols = append(row.Cols, termui.NewCol(12/cols, 0, charts[i])) } + termui.Body.AddRows(termui.NewRow(termui.NewCol(12, 0, footer))) termui.Body.Align() termui.Render(termui.Body) - refresh := time.Tick(time.Second) + refreshCharts(xeth, monitored, data, charts, ctx, footer) + termui.Render(termui.Body) + + // Watch for various system events, and periodically refresh the charts + refresh := time.Tick(time.Duration(ctx.Int(monitorCommandRefreshFlag.Name)) * time.Second) for { select { case event := <-termui.EventCh(): @@ -133,20 +132,13 @@ func monitor(ctx *cli.Context) { if event.Type == termui.EventResize { termui.Body.Width = termui.TermWidth() for _, chart := range charts { - chart.Height = termui.TermHeight() / rows + chart.Height = (termui.TermHeight() - footer.Height) / rows } termui.Body.Align() termui.Render(termui.Body) } case <-refresh: - metrics, err := retrieveMetrics(xeth) - if err != nil { - utils.Fatalf("Failed to retrieve system metrics: %v", err) - } - for i, metric := range monitored { - data[i] = append([]float64{value(metrics, metric)}, data[i][:len(data[i])-1]...) - updateChart(metric, data[i], charts[i]) - } + refreshCharts(xeth, monitored, data, charts, ctx, footer) termui.Render(termui.Body) } } @@ -226,13 +218,42 @@ func expandMetrics(metrics map[string]interface{}, path string) []string { return list } +// fetchMetric iterates over the metrics map and retrieves a specific one. +func fetchMetric(metrics map[string]interface{}, metric string) float64 { + parts, found := strings.Split(metric, "/"), true + for _, part := range parts[:len(parts)-1] { + metrics, found = metrics[part].(map[string]interface{}) + if !found { + return 0 + } + } + if v, ok := metrics[parts[len(parts)-1]].(float64); ok { + return v + } + return 0 +} + +// refreshCharts retrieves a next batch of metrics, and inserts all the new +// values into the active datasets and charts +func refreshCharts(xeth *rpc.Xeth, metrics []string, data [][]float64, charts []*termui.LineChart, ctx *cli.Context, footer *termui.Par) { + values, err := retrieveMetrics(xeth) + for i, metric := range metrics { + data[i] = append([]float64{fetchMetric(values, metric)}, data[i][:len(data[i])-1]...) + updateChart(metric, data[i], charts[i], err) + } + updateFooter(ctx, err, footer) +} + // updateChart inserts a dataset into a line chart, scaling appropriately as to // not display weird labels, also updating the chart label accordingly. -func updateChart(metric string, data []float64, chart *termui.LineChart) { +func updateChart(metric string, data []float64, chart *termui.LineChart, err error) { dataUnits := []string{"", "K", "M", "G", "T", "E"} timeUnits := []string{"ns", "µs", "ms", "s", "ks", "ms"} colors := []termui.Attribute{termui.ColorBlue, termui.ColorCyan, termui.ColorGreen, termui.ColorYellow, termui.ColorRed, termui.ColorRed} + // Extract only part of the data that's actually visible + data = data[:chart.Width*2] + // Find the maximum value and scale under 1K high := data[0] for _, value := range data[1:] { @@ -256,5 +277,22 @@ func updateChart(metric string, data []float64, chart *termui.LineChart) { if len(units[unit]) > 0 { chart.Border.Label += " [" + units[unit] + "]" } - chart.LineColor = colors[unit] + chart.LineColor = colors[unit] | termui.AttrBold + if err != nil { + chart.LineColor = termui.ColorRed | termui.AttrBold + } +} + +// updateFooter updates the footer contents based on any encountered errors. +func updateFooter(ctx *cli.Context, err error, footer *termui.Par) { + // Generate the basic footer + refresh := time.Duration(ctx.Int(monitorCommandRefreshFlag.Name)) * time.Second + footer.Text = fmt.Sprintf("Press q to quit. Refresh interval: %v.", refresh) + footer.TextFgColor = termui.Theme().ParTextFg | termui.AttrBold + + // Append any encountered errors + if err != nil { + footer.Text = fmt.Sprintf("Error: %v.", err) + footer.TextFgColor = termui.ColorRed | termui.AttrBold + } } -- cgit v1.2.3 From 3ea6b5ae32215daa6393e02682d6946a2aa89af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Jun 2015 11:42:45 +0300 Subject: cmd/geth: list the available metrics if none specified --- cmd/geth/monitorcmd.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'cmd') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 492865d0e..b51da91e6 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -71,6 +71,19 @@ func monitor(ctx *cli.Context) { utils.Fatalf("Failed to retrieve system metrics: %v", err) } monitored := resolveMetrics(metrics, ctx.Args()) + if len(monitored) == 0 { + list := []string{} + for _, metric := range expandMetrics(metrics, "") { + switch { + case strings.HasSuffix(metric, "/0"): + list = append(list, strings.Replace(metric, "/0", "/[0-100]", -1)) + case !strings.Contains(metric, "Percentiles"): + list = append(list, metric) + } + } + sort.Strings(list) + utils.Fatalf("No metrics specified.\n\nAvailable:\n - %s", strings.Join(list, "\n - ")) + } sort.Strings(monitored) // Create and configure the chart UI defaults -- cgit v1.2.3 From c6e2af14c0019c43c11e03b9fd79ba4489a38bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Jun 2015 12:12:11 +0300 Subject: cmd/geth: limit the maximum chart colums to 6 --- cmd/geth/monitorcmd.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index b51da91e6..78b67c17c 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -85,7 +85,9 @@ func monitor(ctx *cli.Context) { utils.Fatalf("No metrics specified.\n\nAvailable:\n - %s", strings.Join(list, "\n - ")) } sort.Strings(monitored) - + if cols := len(monitored) / ctx.Int(monitorCommandRowsFlag.Name); cols > 6 { + utils.Fatalf("Requested metrics (%d) spans more that 6 columns:\n - %s", len(monitored), strings.Join(monitored, "\n - ")) + } // Create and configure the chart UI defaults if err := termui.Init(); err != nil { utils.Fatalf("Unable to initialize terminal UI: %v", err) -- cgit v1.2.3 From c0343c8f17de0b896d4c0546921881a92ce4ae1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Jun 2015 13:47:06 +0300 Subject: cmd/geth: add memory stat collection too --- cmd/geth/main.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'cmd') diff --git a/cmd/geth/main.go b/cmd/geth/main.go index f1c229d1f..53f6a95d9 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -30,6 +30,7 @@ import ( "runtime" "strconv" "strings" + "time" "github.com/codegangsta/cli" "github.com/ethereum/ethash" @@ -42,6 +43,7 @@ import ( "github.com/ethereum/go-ethereum/rpc/comms" "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" + "github.com/rcrowley/go-metrics" ) const ( @@ -285,6 +287,28 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso } return nil } + // Start system runtime metrics collection + go func() { + used := metrics.GetOrRegisterMeter("system/memory/used", metrics.DefaultRegistry) + total := metrics.GetOrRegisterMeter("system/memory/total", metrics.DefaultRegistry) + mallocs := metrics.GetOrRegisterMeter("system/memory/mallocs", metrics.DefaultRegistry) + frees := metrics.GetOrRegisterMeter("system/memory/frees", metrics.DefaultRegistry) + + stats := make([]*runtime.MemStats, 2) + for i := 0; i < len(stats); i++ { + stats[i] = new(runtime.MemStats) + } + for i := 1; ; i++ { + runtime.ReadMemStats(stats[i%2]) + + used.Mark(int64(stats[i%2].Alloc - stats[(i-1)%2].Alloc)) + total.Mark(int64(stats[i%2].TotalAlloc - stats[(i-1)%2].TotalAlloc)) + mallocs.Mark(int64(stats[i%2].Mallocs - stats[(i-1)%2].Mallocs)) + frees.Mark(int64(stats[i%2].Frees - stats[(i-1)%2].Frees)) + + time.Sleep(3 * time.Second) + } + }() } func main() { -- cgit v1.2.3 From fdbf8be7356cb8a80c6fdfe0d24b0863903e1832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Jun 2015 15:33:26 +0300 Subject: cmd/geth, rpc/api: fix reported metrics issues --- cmd/geth/monitorcmd.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) (limited to 'cmd') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 78b67c17c..43937dcaa 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "reflect" + "runtime" "sort" "strings" "time" @@ -72,15 +73,7 @@ func monitor(ctx *cli.Context) { } monitored := resolveMetrics(metrics, ctx.Args()) if len(monitored) == 0 { - list := []string{} - for _, metric := range expandMetrics(metrics, "") { - switch { - case strings.HasSuffix(metric, "/0"): - list = append(list, strings.Replace(metric, "/0", "/[0-100]", -1)) - case !strings.Contains(metric, "Percentiles"): - list = append(list, metric) - } - } + list := expandMetrics(metrics, "") sort.Strings(list) utils.Fatalf("No metrics specified.\n\nAvailable:\n - %s", strings.Join(list, "\n - ")) } @@ -116,11 +109,14 @@ func monitor(ctx *cli.Context) { } for i, metric := range monitored { charts[i] = termui.NewLineChart() + if runtime.GOOS == "windows" { + charts[i].Mode = "dot" + } charts[i].Data = make([]float64, 512) charts[i].DataLabels = []string{""} charts[i].Height = (termui.TermHeight() - footer.Height) / rows charts[i].AxesColor = termui.ColorWhite - charts[i].PaddingBottom = -1 + charts[i].PaddingBottom = -2 charts[i].Border.Label = metric charts[i].Border.LabelFgColor = charts[i].Border.FgColor | termui.AttrBold @@ -141,7 +137,7 @@ func monitor(ctx *cli.Context) { for { select { case event := <-termui.EventCh(): - if event.Type == termui.EventKey && event.Ch == 'q' { + if event.Type == termui.EventKey && event.Key == termui.KeyCtrlC { return } if event.Type == termui.EventResize { @@ -302,7 +298,7 @@ func updateChart(metric string, data []float64, chart *termui.LineChart, err err func updateFooter(ctx *cli.Context, err error, footer *termui.Par) { // Generate the basic footer refresh := time.Duration(ctx.Int(monitorCommandRefreshFlag.Name)) * time.Second - footer.Text = fmt.Sprintf("Press q to quit. Refresh interval: %v.", refresh) + footer.Text = fmt.Sprintf("Press Ctrl+C to quit. Refresh interval: %v.", refresh) footer.TextFgColor = termui.Theme().ParTextFg | termui.AttrBold // Append any encountered errors -- cgit v1.2.3 From e9c0b5431cbd7430ddec9fd17983241018fd8a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Jun 2015 16:19:42 +0300 Subject: cmd/geth: finalize mem stats --- cmd/geth/main.go | 12 ++++++------ cmd/geth/monitorcmd.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'cmd') diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 53f6a95d9..fcf7f27f0 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -289,10 +289,10 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso } // Start system runtime metrics collection go func() { - used := metrics.GetOrRegisterMeter("system/memory/used", metrics.DefaultRegistry) - total := metrics.GetOrRegisterMeter("system/memory/total", metrics.DefaultRegistry) - mallocs := metrics.GetOrRegisterMeter("system/memory/mallocs", metrics.DefaultRegistry) + allocs := metrics.GetOrRegisterMeter("system/memory/allocs", metrics.DefaultRegistry) frees := metrics.GetOrRegisterMeter("system/memory/frees", metrics.DefaultRegistry) + inuse := metrics.GetOrRegisterMeter("system/memory/inuse", metrics.DefaultRegistry) + pauses := metrics.GetOrRegisterMeter("system/memory/pauses", metrics.DefaultRegistry) stats := make([]*runtime.MemStats, 2) for i := 0; i < len(stats); i++ { @@ -301,10 +301,10 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso for i := 1; ; i++ { runtime.ReadMemStats(stats[i%2]) - used.Mark(int64(stats[i%2].Alloc - stats[(i-1)%2].Alloc)) - total.Mark(int64(stats[i%2].TotalAlloc - stats[(i-1)%2].TotalAlloc)) - mallocs.Mark(int64(stats[i%2].Mallocs - stats[(i-1)%2].Mallocs)) + allocs.Mark(int64(stats[i%2].Mallocs - stats[(i-1)%2].Mallocs)) frees.Mark(int64(stats[i%2].Frees - stats[(i-1)%2].Frees)) + inuse.Mark(int64(stats[i%2].Alloc - stats[(i-1)%2].Alloc)) + pauses.Mark(int64(stats[i%2].PauseTotalNs - stats[(i-1)%2].PauseTotalNs)) time.Sleep(3 * time.Second) } diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 43937dcaa..bb9c61a00 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -282,7 +282,7 @@ func updateChart(metric string, data []float64, chart *termui.LineChart, err err chart.Border.Label = metric units := dataUnits - if strings.Contains(metric, "Percentiles") { + if strings.Contains(metric, "/Percentiles/") || strings.Contains(metric, "/pauses/") { units = timeUnits } if len(units[unit]) > 0 { -- cgit v1.2.3 From 7e69392249b37369a1dd6e10bb31b7e2e655b05d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 26 Jun 2015 21:48:21 +0300 Subject: cmd/geth: re-scale charts when changing unit magnitudes --- cmd/geth/monitorcmd.go | 62 +++++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 24 deletions(-) (limited to 'cmd') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index bb9c61a00..545ac3274 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -103,33 +103,20 @@ func monitor(ctx *cli.Context) { footer.Height = 3 charts := make([]*termui.LineChart, len(monitored)) + units := make([]int, len(monitored)) data := make([][]float64, len(monitored)) for i := 0; i < len(data); i++ { data[i] = make([]float64, 512) } - for i, metric := range monitored { - charts[i] = termui.NewLineChart() - if runtime.GOOS == "windows" { - charts[i].Mode = "dot" - } - charts[i].Data = make([]float64, 512) - charts[i].DataLabels = []string{""} - charts[i].Height = (termui.TermHeight() - footer.Height) / rows - charts[i].AxesColor = termui.ColorWhite - charts[i].PaddingBottom = -2 - - charts[i].Border.Label = metric - charts[i].Border.LabelFgColor = charts[i].Border.FgColor | termui.AttrBold - charts[i].Border.FgColor = charts[i].Border.BgColor - + for i := 0; i < len(monitored); i++ { + charts[i] = createChart((termui.TermHeight() - footer.Height) / rows) row := termui.Body.Rows[i%rows] row.Cols = append(row.Cols, termui.NewCol(12/cols, 0, charts[i])) } termui.Body.AddRows(termui.NewRow(termui.NewCol(12, 0, footer))) - termui.Body.Align() - termui.Render(termui.Body) - refreshCharts(xeth, monitored, data, charts, ctx, footer) + refreshCharts(xeth, monitored, data, units, charts, ctx, footer) + termui.Body.Align() termui.Render(termui.Body) // Watch for various system events, and periodically refresh the charts @@ -149,7 +136,9 @@ func monitor(ctx *cli.Context) { termui.Render(termui.Body) } case <-refresh: - refreshCharts(xeth, monitored, data, charts, ctx, footer) + if refreshCharts(xeth, monitored, data, units, charts, ctx, footer) { + termui.Body.Align() + } termui.Render(termui.Body) } } @@ -246,18 +235,21 @@ func fetchMetric(metrics map[string]interface{}, metric string) float64 { // refreshCharts retrieves a next batch of metrics, and inserts all the new // values into the active datasets and charts -func refreshCharts(xeth *rpc.Xeth, metrics []string, data [][]float64, charts []*termui.LineChart, ctx *cli.Context, footer *termui.Par) { +func refreshCharts(xeth *rpc.Xeth, metrics []string, data [][]float64, units []int, charts []*termui.LineChart, ctx *cli.Context, footer *termui.Par) (realign bool) { values, err := retrieveMetrics(xeth) for i, metric := range metrics { data[i] = append([]float64{fetchMetric(values, metric)}, data[i][:len(data[i])-1]...) - updateChart(metric, data[i], charts[i], err) + if updateChart(metric, data[i], &units[i], charts[i], err) { + realign = true + } } updateFooter(ctx, err, footer) + return } // updateChart inserts a dataset into a line chart, scaling appropriately as to // not display weird labels, also updating the chart label accordingly. -func updateChart(metric string, data []float64, chart *termui.LineChart, err error) { +func updateChart(metric string, data []float64, base *int, chart *termui.LineChart, err error) (realign bool) { dataUnits := []string{"", "K", "M", "G", "T", "E"} timeUnits := []string{"ns", "µs", "ms", "s", "ks", "ms"} colors := []termui.Attribute{termui.ColorBlue, termui.ColorCyan, termui.ColorGreen, termui.ColorYellow, termui.ColorRed, termui.ColorRed} @@ -274,17 +266,20 @@ func updateChart(metric string, data []float64, chart *termui.LineChart, err err for high >= 1000 { high, unit, scale = high/1000, unit+1, scale*1000 } + // If the unit changes, re-create the chart (hack to set max height...) + if unit != *base { + realign, *base, *chart = true, unit, *createChart(chart.Height) + } // Update the chart's data points with the scaled values for i, value := range data { chart.Data[i] = value / scale } // Update the chart's label with the scale units - chart.Border.Label = metric - units := dataUnits if strings.Contains(metric, "/Percentiles/") || strings.Contains(metric, "/pauses/") { units = timeUnits } + chart.Border.Label = metric if len(units[unit]) > 0 { chart.Border.Label += " [" + units[unit] + "]" } @@ -292,6 +287,25 @@ func updateChart(metric string, data []float64, chart *termui.LineChart, err err if err != nil { chart.LineColor = termui.ColorRed | termui.AttrBold } + return +} + +// createChart creates an empty line chart with the default configs. +func createChart(height int) *termui.LineChart { + chart := termui.NewLineChart() + if runtime.GOOS == "windows" { + chart.Mode = "dot" + } + chart.Data = make([]float64, 512) + chart.DataLabels = []string{""} + chart.Height = height + chart.AxesColor = termui.ColorWhite + chart.PaddingBottom = -2 + + chart.Border.LabelFgColor = chart.Border.FgColor | termui.AttrBold + chart.Border.FgColor = chart.Border.BgColor + + return chart } // updateFooter updates the footer contents based on any encountered errors. -- cgit v1.2.3 From d099a42c8564405f2f08b83ec67e0fd96fb03732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 26 Jun 2015 22:05:49 +0300 Subject: cmd/geth: fix monitor panic, don't pre-fill with dummy data --- cmd/geth/monitorcmd.go | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) (limited to 'cmd') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 545ac3274..fe771b561 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -105,9 +105,6 @@ func monitor(ctx *cli.Context) { charts := make([]*termui.LineChart, len(monitored)) units := make([]int, len(monitored)) data := make([][]float64, len(monitored)) - for i := 0; i < len(data); i++ { - data[i] = make([]float64, 512) - } for i := 0; i < len(monitored); i++ { charts[i] = createChart((termui.TermHeight() - footer.Height) / rows) row := termui.Body.Rows[i%rows] @@ -238,7 +235,11 @@ func fetchMetric(metrics map[string]interface{}, metric string) float64 { func refreshCharts(xeth *rpc.Xeth, metrics []string, data [][]float64, units []int, charts []*termui.LineChart, ctx *cli.Context, footer *termui.Par) (realign bool) { values, err := retrieveMetrics(xeth) for i, metric := range metrics { - data[i] = append([]float64{fetchMetric(values, metric)}, data[i][:len(data[i])-1]...) + if len(data) < 512 { + data[i] = append([]float64{fetchMetric(values, metric)}, data[i]...) + } else { + data[i] = append([]float64{fetchMetric(values, metric)}, data[i][:len(data[i])-1]...) + } if updateChart(metric, data[i], &units[i], charts[i], err) { realign = true } @@ -255,12 +256,16 @@ func updateChart(metric string, data []float64, base *int, chart *termui.LineCha colors := []termui.Attribute{termui.ColorBlue, termui.ColorCyan, termui.ColorGreen, termui.ColorYellow, termui.ColorRed, termui.ColorRed} // Extract only part of the data that's actually visible - data = data[:chart.Width*2] - + if chart.Width*2 < len(data) { + data = data[:chart.Width*2] + } // Find the maximum value and scale under 1K - high := data[0] - for _, value := range data[1:] { - high = math.Max(high, value) + high := 0.0 + if len(data) > 0 { + high = data[0] + for _, value := range data[1:] { + high = math.Max(high, value) + } } unit, scale := 0, 1.0 for high >= 1000 { @@ -271,6 +276,10 @@ func updateChart(metric string, data []float64, base *int, chart *termui.LineCha realign, *base, *chart = true, unit, *createChart(chart.Height) } // Update the chart's data points with the scaled values + if cap(chart.Data) < len(data) { + chart.Data = make([]float64, len(data)) + } + chart.Data = chart.Data[:len(data)] for i, value := range data { chart.Data[i] = value / scale } @@ -296,7 +305,6 @@ func createChart(height int) *termui.LineChart { if runtime.GOOS == "windows" { chart.Mode = "dot" } - chart.Data = make([]float64, 512) chart.DataLabels = []string{""} chart.Height = height chart.AxesColor = termui.ColorWhite -- cgit v1.2.3 From 2aeeb72fa5c4f90d0ab072a361a678c3cdee8e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Sat, 27 Jun 2015 18:12:58 +0300 Subject: cmd/geth, metrics: separate process metric collection, add disk --- cmd/geth/main.go | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) (limited to 'cmd') diff --git a/cmd/geth/main.go b/cmd/geth/main.go index fcf7f27f0..6a52159ea 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -39,11 +39,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/comms" "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" - "github.com/rcrowley/go-metrics" ) const ( @@ -288,27 +288,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso return nil } // Start system runtime metrics collection - go func() { - allocs := metrics.GetOrRegisterMeter("system/memory/allocs", metrics.DefaultRegistry) - frees := metrics.GetOrRegisterMeter("system/memory/frees", metrics.DefaultRegistry) - inuse := metrics.GetOrRegisterMeter("system/memory/inuse", metrics.DefaultRegistry) - pauses := metrics.GetOrRegisterMeter("system/memory/pauses", metrics.DefaultRegistry) - - stats := make([]*runtime.MemStats, 2) - for i := 0; i < len(stats); i++ { - stats[i] = new(runtime.MemStats) - } - for i := 1; ; i++ { - runtime.ReadMemStats(stats[i%2]) - - allocs.Mark(int64(stats[i%2].Mallocs - stats[(i-1)%2].Mallocs)) - frees.Mark(int64(stats[i%2].Frees - stats[(i-1)%2].Frees)) - inuse.Mark(int64(stats[i%2].Alloc - stats[(i-1)%2].Alloc)) - pauses.Mark(int64(stats[i%2].PauseTotalNs - stats[(i-1)%2].PauseTotalNs)) - - time.Sleep(3 * time.Second) - } - }() + go metrics.CollectProcessMetrics(3 * time.Second) } func main() { -- cgit v1.2.3 From ccbb56b4f2cdba352eaa859ce3e34f999287f5c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Sat, 27 Jun 2015 20:03:31 +0300 Subject: cmd/geth, eth, ethdb: monitor database compactions --- cmd/geth/monitorcmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index fe771b561..05965f009 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -285,7 +285,7 @@ func updateChart(metric string, data []float64, base *int, chart *termui.LineCha } // Update the chart's label with the scale units units := dataUnits - if strings.Contains(metric, "/Percentiles/") || strings.Contains(metric, "/pauses/") { + if strings.Contains(metric, "/Percentiles/") || strings.Contains(metric, "/pauses/") || strings.Contains(metric, "/time/") { units = timeUnits } chart.Border.Label = metric -- cgit v1.2.3 From 01fe97211354d13ecaba8a52c82b808b7a7e8393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 29 Jun 2015 16:11:01 +0300 Subject: cmd, core, eth, metrics, p2p: require enabling metrics --- cmd/geth/main.go | 1 + cmd/geth/monitorcmd.go | 7 ++++++- cmd/utils/flags.go | 6 ++++++ 3 files changed, 13 insertions(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 6a52159ea..3e945687b 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -272,6 +272,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.LogJSONFlag, utils.PProfEanbledFlag, utils.PProfPortFlag, + utils.MetricsEnabledFlag, utils.SolcPathFlag, utils.GpoMinGasPriceFlag, utils.GpoMaxGasPriceFlag, diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 05965f009..6a7e1cbb4 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -75,7 +75,12 @@ func monitor(ctx *cli.Context) { if len(monitored) == 0 { list := expandMetrics(metrics, "") sort.Strings(list) - utils.Fatalf("No metrics specified.\n\nAvailable:\n - %s", strings.Join(list, "\n - ")) + + if len(list) > 0 { + utils.Fatalf("No metrics specified.\n\nAvailable:\n - %s", strings.Join(list, "\n - ")) + } else { + utils.Fatalf("No metrics specified.\n\nNo metrics collected (--metrics)\n") + } } sort.Strings(monitored) if cols := len(monitored) / ctx.Int(monitorCommandRowsFlag.Name); cols > 6 { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 15a577a07..0d59980ec 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -10,6 +10,8 @@ import ( "path/filepath" "runtime" + "github.com/ethereum/go-ethereum/metrics" + "github.com/codegangsta/cli" "github.com/ethereum/ethash" "github.com/ethereum/go-ethereum/accounts" @@ -187,6 +189,10 @@ var ( Usage: "Port on which the profiler should listen", Value: 6060, } + MetricsEnabledFlag = cli.BoolFlag{ + Name: metrics.MetricsEnabledFlag, + Usage: "Enables metrics collection and reporting", + } // RPC settings RPCEnabledFlag = cli.BoolFlag{ -- cgit v1.2.3 From 5f3792c2a750dd95adeccbd5cf0cb19ecddfb43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 29 Jun 2015 16:18:34 +0300 Subject: cmd/geth: decent error message if metrics are disabled --- cmd/geth/monitorcmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 6a7e1cbb4..6593b3614 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -79,7 +79,7 @@ func monitor(ctx *cli.Context) { if len(list) > 0 { utils.Fatalf("No metrics specified.\n\nAvailable:\n - %s", strings.Join(list, "\n - ")) } else { - utils.Fatalf("No metrics specified.\n\nNo metrics collected (--metrics)\n") + utils.Fatalf("No metrics collected by geth (--%s).\n", utils.MetricsEnabledFlag.Name) } } sort.Strings(monitored) -- cgit v1.2.3 From 8f504063f465e0ca10c6bb53ee914d10a3d45c86 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 30 Jun 2015 02:11:54 +0200 Subject: cmd/geth: version bump 0.9.34 --- cmd/geth/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 3e945687b..f4d2f94fe 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -48,7 +48,7 @@ import ( const ( ClientIdentifier = "Geth" - Version = "0.9.33" + Version = "0.9.34" ) var ( -- cgit v1.2.3