Browse Source

Merge pull request #44 from tendermint/bugfix/ws-io-timeout

update WSClient plus fix IO timeout error
pull/1943/head
Anton Kaliaev 7 years ago
committed by GitHub
parent
commit
43f8ea58ba
21 changed files with 243 additions and 228 deletions
  1. +2
    -0
      README.md
  2. +2
    -2
      mintnet-kubernetes/app.template.yaml
  3. +4
    -4
      mintnet-kubernetes/examples/basecoin/app.yaml
  4. +2
    -2
      mintnet-kubernetes/examples/counter/app.yaml
  5. +2
    -2
      mintnet-kubernetes/examples/dummy/app.yaml
  6. +1
    -1
      tm-bench/Dockerfile.dev
  7. +2
    -2
      tm-bench/Makefile
  8. +29
    -22
      tm-bench/glide.lock
  9. +3
    -5
      tm-bench/glide.yaml
  10. +1
    -1
      tm-bench/main.go
  11. +55
    -16
      tm-bench/transacter.go
  12. +1
    -1
      tm-monitor/Dockerfile.dev
  13. +2
    -2
      tm-monitor/Makefile
  14. +114
    -138
      tm-monitor/eventmeter/eventmeter.go
  15. +14
    -6
      tm-monitor/glide.lock
  16. +1
    -1
      tm-monitor/glide.yaml
  17. +2
    -2
      tm-monitor/main.go
  18. +0
    -0
      tm-monitor/mock/eventmeter.go
  19. +4
    -15
      tm-monitor/monitor/node.go
  20. +1
    -4
      tm-monitor/monitor/node_test.go
  21. +1
    -2
      tm-monitor/rpc.go

+ 2
- 0
README.md View File

@ -0,0 +1,2 @@
# tools
Tools for working with tendermint and associated technologies

+ 2
- 2
mintnet-kubernetes/app.template.yaml View File

@ -129,11 +129,11 @@ spec:
# wait until validator generates priv/pub key pair # wait until validator generates priv/pub key pair
set +e set +e
curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
ERR=$? ERR=$?
while [ "$ERR" != 0 ]; do while [ "$ERR" != 0 ]; do
sleep 5 sleep 5
curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
ERR=$? ERR=$?
done done
set -e set -e


+ 4
- 4
mintnet-kubernetes/examples/basecoin/app.yaml View File

@ -188,11 +188,11 @@ spec:
# wait until validator generates priv/pub key pair # wait until validator generates priv/pub key pair
set +e set +e
curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
ERR=$? ERR=$?
while [ "$ERR" != 0 ]; do while [ "$ERR" != 0 ]; do
sleep 5 sleep 5
curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
ERR=$? ERR=$?
done done
set -e set -e
@ -247,11 +247,11 @@ spec:
# wait until pod starts to serve its pub_key # wait until pod starts to serve its pub_key
set +e set +e
curl -s "http://$pod.$fqdn_suffix/app_pub_key.json" > /dev/null
curl -s --fail "http://$pod.$fqdn_suffix/app_pub_key.json" > /dev/null
ERR=$? ERR=$?
while [ "$ERR" != 0 ]; do while [ "$ERR" != 0 ]; do
sleep 5 sleep 5
curl -s "http://$pod.$fqdn_suffix/app_pub_key.json" > /dev/null
curl -s --fail "http://$pod.$fqdn_suffix/app_pub_key.json" > /dev/null
ERR=$? ERR=$?
done done
set -e set -e


+ 2
- 2
mintnet-kubernetes/examples/counter/app.yaml View File

@ -121,11 +121,11 @@ spec:
# wait until validator generates priv/pub key pair # wait until validator generates priv/pub key pair
set +e set +e
curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
ERR=$? ERR=$?
while [ "$ERR" != 0 ]; do while [ "$ERR" != 0 ]; do
sleep 5 sleep 5
curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
ERR=$? ERR=$?
done done
set -e set -e


+ 2
- 2
mintnet-kubernetes/examples/dummy/app.yaml View File

@ -121,11 +121,11 @@ spec:
# wait until validator generates priv/pub key pair # wait until validator generates priv/pub key pair
set +e set +e
curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
ERR=$? ERR=$?
while [ "$ERR" != 0 ]; do while [ "$ERR" != 0 ]; do
sleep 5 sleep 5
curl -s "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
curl -s --fail "http://$v.$fqdn_suffix/pub_key.json" > /dev/null
ERR=$? ERR=$?
done done
set -e set -e


+ 1
- 1
tm-bench/Dockerfile.dev View File

@ -7,6 +7,6 @@ COPY Makefile /go/src/github.com/tendermint/tools/tm-bench/
COPY glide.yaml /go/src/github.com/tendermint/tools/tm-bench/ COPY glide.yaml /go/src/github.com/tendermint/tools/tm-bench/
COPY glide.lock /go/src/github.com/tendermint/tools/tm-bench/ COPY glide.lock /go/src/github.com/tendermint/tools/tm-bench/
RUN make get_deps
RUN make get_vendor_deps
COPY . /go/src/github.com/tendermint/tools/tm-bench COPY . /go/src/github.com/tendermint/tools/tm-bench

+ 2
- 2
tm-bench/Makefile View File

@ -7,7 +7,7 @@ GOTOOLS = \
tools: tools:
go get -v $(GOTOOLS) go get -v $(GOTOOLS)
get_deps: tools
get_vendor_deps: tools
glide install glide install
build: build:
@ -44,4 +44,4 @@ clean:
rm -f ./tm-bench rm -f ./tm-bench
rm -rf ./dist rm -rf ./dist
.PHONY: tools get_deps build install test build-all dist clean build-docker
.PHONY: tools get_vendor_deps build install test build-all dist clean build-docker

+ 29
- 22
tm-bench/glide.lock View File

@ -1,22 +1,28 @@
hash: b963733b341869e0667dde0c93f9be17fdf002ab4e92ae8778562a2b94580de8
updated: 2017-07-29T18:52:35.221739544Z
hash: 765fd22d79f7d7123197548b3228ebf56f72be9541b64b04cde875f2d09214f8
updated: 2017-10-06T07:40:33.279710782Z
imports: imports:
- name: github.com/btcsuite/btcd - name: github.com/btcsuite/btcd
version: 47885ab8702485be6b6f87a03d4f3be0bc5c982c
version: 4803a8291c92a1d2d41041b942a9a9e37deab065
subpackages: subpackages:
- btcec - btcec
- name: github.com/go-kit/kit - name: github.com/go-kit/kit
version: 19463ea8b215413a29c3513aa3a76181f4bac58d
version: 4dc7be5d2d12881735283bcab7352178e190fc71
subpackages: subpackages:
- log - log
- log/level - log/level
- log/term - log/term
- name: github.com/go-logfmt/logfmt - name: github.com/go-logfmt/logfmt
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
- name: github.com/go-playground/locales
version: 1e5f1161c6416a5ff48840eb8724a394e48cc534
subpackages:
- currency
- name: github.com/go-playground/universal-translator
version: 71201497bace774495daed26a3874fd339e0b538
- name: github.com/go-stack/stack - name: github.com/go-stack/stack
version: 817915b46b97fd7bb80e8ab6b69f01a53ac3eebf version: 817915b46b97fd7bb80e8ab6b69f01a53ac3eebf
- name: github.com/golang/protobuf - name: github.com/golang/protobuf
version: 748d386b5c1ea99658fd69fe9f03991ce86a90c1
version: 130e6b02ab059e7b717a096f397c5b60111cae74
subpackages: subpackages:
- proto - proto
- ptypes - ptypes
@ -24,15 +30,15 @@ imports:
- ptypes/duration - ptypes/duration
- ptypes/timestamp - ptypes/timestamp
- name: github.com/gorilla/websocket - name: github.com/gorilla/websocket
version: a69d9f6de432e2c6b296a947d8a5ee88f68522cf
version: 4201258b820c74ac8e6922fc9e6b52f71fe46f8d
- name: github.com/kr/logfmt - name: github.com/kr/logfmt
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/pkg/errors - name: github.com/pkg/errors
version: c605e284fe17294bda444b34710735b29d1a9d90
version: 2b3a18b5f0fb6b4f9190549597d3f962c02bc5eb
- name: github.com/rcrowley/go-metrics - name: github.com/rcrowley/go-metrics
version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c
- name: github.com/tendermint/abci - name: github.com/tendermint/abci
version: 864d1f80b36b440bde030a5c18d8ac3aa8c2949d
version: 191c4b6d176169ffc7f9972d490fa362a3b7d940
subpackages: subpackages:
- client - client
- example/dummy - example/dummy
@ -43,17 +49,13 @@ imports:
- edwards25519 - edwards25519
- extra25519 - extra25519
- name: github.com/tendermint/go-crypto - name: github.com/tendermint/go-crypto
version: 95b7c9e09c49b91bfbb71bb63dd514eb55450f16
- name: github.com/tendermint/go-rpc
version: 15d5b2ac497da95cd2dceb9c087910ccec4dacb2
subpackages:
- types
version: 311e8c1bf00fa5868daad4f8ea56dcad539182c0
- name: github.com/tendermint/go-wire - name: github.com/tendermint/go-wire
version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb
subpackages: subpackages:
- data - data
- name: github.com/tendermint/tendermint - name: github.com/tendermint/tendermint
version: b467515719e686e4678e6da4e102f32a491b85a0
version: 7682ad9a60162dd17fd6f61aeed7049a8635ac78
subpackages: subpackages:
- config - config
- p2p - p2p
@ -63,7 +65,7 @@ imports:
- rpc/lib/types - rpc/lib/types
- types - types
- name: github.com/tendermint/tmlibs - name: github.com/tendermint/tmlibs
version: 7ce4da1eee6004d627e780c8fe91e96d9b99e459
version: 096dcb90e60aa00b748b3fe49a4b95e48ebf1e13
subpackages: subpackages:
- common - common
- events - events
@ -71,12 +73,12 @@ imports:
- log - log
- merkle - merkle
- name: github.com/tendermint/tools - name: github.com/tendermint/tools
version: d205ae1f98c946b2a057f62bfcd505b40ea52031
version: 9708c66576d3e7d4fd0a5cdec7d951f1ef002efc
subpackages: subpackages:
- tm-monitor/eventmeter - tm-monitor/eventmeter
- tm-monitor/monitor - tm-monitor/monitor
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: 558b6879de74bc843225cde5686419267ff707ca
version: 9419663f5a44be8b34ca85f08abc5fe1be11f8a3
subpackages: subpackages:
- curve25519 - curve25519
- nacl/box - nacl/box
@ -87,7 +89,7 @@ imports:
- ripemd160 - ripemd160
- salsa20/salsa - salsa20/salsa
- name: golang.org/x/net - name: golang.org/x/net
version: f5079bd7f6f74e23c4d65efa0f4ce14cbd6a3c0f
version: a04bdaca5b32abe1c069418fb7088ae607de5bd0
subpackages: subpackages:
- context - context
- http2 - http2
@ -97,30 +99,35 @@ imports:
- lex/httplex - lex/httplex
- trace - trace
- name: golang.org/x/text - name: golang.org/x/text
version: 836efe42bb4aa16aaa17b9c155d8813d336ed720
version: d82c1812e304abfeeabd31e995a115a2855bf642
subpackages: subpackages:
- secure/bidirule - secure/bidirule
- transform - transform
- unicode/bidi - unicode/bidi
- unicode/norm - unicode/norm
- name: google.golang.org/genproto - name: google.golang.org/genproto
version: b0a3dcfcd1a9bd48e63634bd8802960804cf8315
version: f676e0f3ac6395ff1a529ae59a6670878a8371a6
subpackages: subpackages:
- googleapis/rpc/status - googleapis/rpc/status
- name: google.golang.org/grpc - name: google.golang.org/grpc
version: 971efedc2078cb1efd8111d12432813084bc628d
version: 5279edf262dc22329b1e53281ce9d55c0a998216
subpackages: subpackages:
- balancer
- codes - codes
- connectivity
- credentials - credentials
- grpclb/grpc_lb_v1
- grpclb/grpc_lb_v1/messages
- grpclog - grpclog
- internal - internal
- keepalive - keepalive
- metadata - metadata
- naming - naming
- peer - peer
- resolver
- stats - stats
- status - status
- tap - tap
- transport - transport
- name: gopkg.in/go-playground/validator.v9
version: a021b2ec9a8a8bb970f3f15bc42617cb520e8a64
testImports: [] testImports: []

+ 3
- 5
tm-bench/glide.yaml View File

@ -6,17 +6,15 @@ import:
- package: github.com/gorilla/websocket - package: github.com/gorilla/websocket
- package: github.com/pkg/errors - package: github.com/pkg/errors
- package: github.com/rcrowley/go-metrics - package: github.com/rcrowley/go-metrics
- package: github.com/tendermint/go-rpc
version: develop
subpackages:
- types
- package: github.com/tendermint/tendermint - package: github.com/tendermint/tendermint
version: v0.11.0
subpackages: subpackages:
- rpc/lib/types
- types - types
- package: github.com/tendermint/tmlibs - package: github.com/tendermint/tmlibs
subpackages: subpackages:
- log - log
- package: github.com/tendermint/tools - package: github.com/tendermint/tools
version: develop
version: 9708c66576d3e7d4fd0a5cdec7d951f1ef002efc
subpackages: subpackages:
- tm-monitor/monitor - tm-monitor/monitor

+ 1
- 1
tm-bench/main.go View File

@ -16,7 +16,7 @@ import (
"github.com/tendermint/tools/tm-monitor/monitor" "github.com/tendermint/tools/tm-monitor/monitor"
) )
var version = "0.1.0"
var version = "0.2.0"
var logger = log.NewNopLogger() var logger = log.NewNopLogger()


+ 55
- 16
tm-bench/transacter.go View File

@ -1,10 +1,13 @@
package main package main
import ( import (
"crypto/md5"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"math/rand" "math/rand"
"net"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@ -14,14 +17,17 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/pkg/errors" "github.com/pkg/errors"
rpctypes "github.com/tendermint/go-rpc/types"
rpctypes "github.com/tendermint/tendermint/rpc/lib/types"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
) )
const ( const (
sendTimeout = 500 * time.Millisecond
sendTimeout = 10 * time.Second
// see https://github.com/tendermint/go-rpc/blob/develop/server/handlers.go#L313 // see https://github.com/tendermint/go-rpc/blob/develop/server/handlers.go#L313
pingPeriod = (30 * 9 / 10) * time.Second pingPeriod = (30 * 9 / 10) * time.Second
// the size of a transaction in bytes.
txSize = 250
) )
type transacter struct { type transacter struct {
@ -56,6 +62,8 @@ func (t *transacter) SetLogger(l log.Logger) {
func (t *transacter) Start() error { func (t *transacter) Start() error {
t.stopped = false t.stopped = false
rand.Seed(time.Now().Unix())
for i := 0; i < t.Connections; i++ { for i := 0; i < t.Connections; i++ {
c, _, err := connect(t.Target) c, _, err := connect(t.Target)
if err != nil { if err != nil {
@ -90,7 +98,7 @@ func (t *transacter) receiveLoop(connIndex int) {
for { for {
_, _, err := c.ReadMessage() _, _, err := c.ReadMessage()
if err != nil { if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {
if !websocket.IsCloseError(err, websocket.CloseNormalClosure) {
t.logger.Error("failed to read response", "err", err) t.logger.Error("failed to read response", "err", err)
} }
return return
@ -104,6 +112,17 @@ func (t *transacter) receiveLoop(connIndex int) {
// sendLoop generates transactions at a given rate. // sendLoop generates transactions at a given rate.
func (t *transacter) sendLoop(connIndex int) { func (t *transacter) sendLoop(connIndex int) {
c := t.conns[connIndex] c := t.conns[connIndex]
c.SetPingHandler(func(message string) error {
err := c.WriteControl(websocket.PongMessage, []byte(message), time.Now().Add(sendTimeout))
if err == websocket.ErrCloseSent {
return nil
} else if e, ok := err.(net.Error); ok && e.Temporary() {
return nil
}
return err
})
logger := t.logger.With("addr", c.RemoteAddr()) logger := t.logger.With("addr", c.RemoteAddr())
var txNumber = 0 var txNumber = 0
@ -116,24 +135,38 @@ func (t *transacter) sendLoop(connIndex int) {
t.wg.Done() t.wg.Done()
}() }()
// hash of the host name is a part of each tx
var hostnameHash [md5.Size]byte
hostname, err := os.Hostname()
if err != nil {
hostname = "127.0.0.1"
}
hostnameHash = md5.Sum([]byte(hostname))
for { for {
select { select {
case <-txsTicker.C: case <-txsTicker.C:
startTime := time.Now() startTime := time.Now()
for i := 0; i < t.Rate; i++ { for i := 0; i < t.Rate; i++ {
// each transaction embeds connection index and tx number
tx := generateTx(connIndex, txNumber)
// each transaction embeds connection index, tx number and hash of the hostname
tx := generateTx(connIndex, txNumber, hostnameHash)
paramsJson, err := json.Marshal(map[string]interface{}{"tx": hex.EncodeToString(tx)})
if err != nil {
fmt.Printf("failed to encode params: %v\n", err)
os.Exit(1)
}
rawParamsJson := json.RawMessage(paramsJson)
c.SetWriteDeadline(time.Now().Add(sendTimeout)) c.SetWriteDeadline(time.Now().Add(sendTimeout))
err := c.WriteJSON(rpctypes.RPCRequest{
err = c.WriteJSON(rpctypes.RPCRequest{
JSONRPC: "2.0", JSONRPC: "2.0",
ID: "",
ID: "tm-bench",
Method: "broadcast_tx_async", Method: "broadcast_tx_async",
Params: []interface{}{hex.EncodeToString(tx)},
Params: &rawParamsJson,
}) })
if err != nil { if err != nil {
fmt.Printf("%v. Try increasing the connections count and reducing the rate.\n", errors.Wrap(err, "txs send failed"))
fmt.Printf("%v. Try reducing the connections count and increasing the rate.\n", errors.Wrap(err, "txs send failed"))
os.Exit(1) os.Exit(1)
} }
@ -144,7 +177,7 @@ func (t *transacter) sendLoop(connIndex int) {
time.Sleep(time.Second - timeToSend) time.Sleep(time.Second - timeToSend)
logger.Info(fmt.Sprintf("sent %d transactions", t.Rate), "took", timeToSend) logger.Info(fmt.Sprintf("sent %d transactions", t.Rate), "took", timeToSend)
case <-pingsTicker.C: case <-pingsTicker.C:
// Right now go-rpc server closes the connection in the absence of pings
// go-rpc server closes the connection in the absence of pings
c.SetWriteDeadline(time.Now().Add(sendTimeout)) c.SetWriteDeadline(time.Now().Add(sendTimeout))
if err := c.WriteMessage(websocket.PingMessage, []byte{}); err != nil { if err := c.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
logger.Error("failed to write ping message", "err", err) logger.Error("failed to write ping message", "err", err)
@ -170,12 +203,18 @@ func connect(host string) (*websocket.Conn, *http.Response, error) {
return websocket.DefaultDialer.Dial(u.String(), nil) return websocket.DefaultDialer.Dial(u.String(), nil)
} }
func generateTx(a int, b int) []byte {
tx := make([]byte, 250)
binary.PutUvarint(tx[:32], uint64(a))
binary.PutUvarint(tx[32:64], uint64(b))
if _, err := rand.Read(tx[234:]); err != nil {
panic(errors.Wrap(err, "failed to generate transaction"))
func generateTx(connIndex int, txNumber int, hostnameHash [md5.Size]byte) []byte {
tx := make([]byte, txSize)
binary.PutUvarint(tx[:8], uint64(connIndex))
binary.PutUvarint(tx[8:16], uint64(txNumber))
copy(tx[16:32], hostnameHash[:16])
binary.PutUvarint(tx[32:40], uint64(time.Now().Unix()))
// 40-* random data
if _, err := rand.Read(tx[40:]); err != nil {
panic(errors.Wrap(err, "failed to read random bytes"))
} }
return tx return tx
} }

+ 1
- 1
tm-monitor/Dockerfile.dev View File

@ -7,6 +7,6 @@ COPY Makefile /go/src/github.com/tendermint/tools/tm-monitor/
COPY glide.yaml /go/src/github.com/tendermint/tools/tm-monitor/ COPY glide.yaml /go/src/github.com/tendermint/tools/tm-monitor/
COPY glide.lock /go/src/github.com/tendermint/tools/tm-monitor/ COPY glide.lock /go/src/github.com/tendermint/tools/tm-monitor/
RUN make get_deps
RUN make get_vendor_deps
COPY . /go/src/github.com/tendermint/tools/tm-monitor COPY . /go/src/github.com/tendermint/tools/tm-monitor

+ 2
- 2
tm-monitor/Makefile View File

@ -8,7 +8,7 @@ PACKAGES=$(shell go list ./... | grep -v '/vendor/')
tools: tools:
go get -v $(GOTOOLS) go get -v $(GOTOOLS)
get_deps: tools
get_vendor_deps: tools
glide install glide install
build: build:
@ -45,4 +45,4 @@ clean:
rm -f ./tm-monitor rm -f ./tm-monitor
rm -rf ./dist rm -rf ./dist
.PHONY: tools get_deps build install test build-all dist clean build-docker
.PHONY: tools get_vendor_deps build install test build-all dist clean build-docker

+ 114
- 138
tm-monitor/eventmeter/eventmeter.go View File

@ -1,30 +1,28 @@
// eventmeter - generic system to subscribe to events and record their frequency.
package eventmeter package eventmeter
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"sync" "sync"
"time" "time"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
metrics "github.com/rcrowley/go-metrics" metrics "github.com/rcrowley/go-metrics"
client "github.com/tendermint/tendermint/rpc/lib/client" client "github.com/tendermint/tendermint/rpc/lib/client"
"github.com/tendermint/tmlibs/events" "github.com/tendermint/tmlibs/events"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
) )
//------------------------------------------------------
// Generic system to subscribe to events and record their frequency
//------------------------------------------------------
const (
// Get ping/pong latency and call LatencyCallbackFunc with this period.
latencyPeriod = 1 * time.Second
//------------------------------------------------------
// Meter for a particular event
// Closure to enable side effects from receiving an event
type EventCallbackFunc func(em *EventMetric, data interface{})
// Check if the WS client is connected every
connectionCheckPeriod = 100 * time.Millisecond
)
// Metrics for a given event
// EventMetric exposes metrics for an event.
type EventMetric struct { type EventMetric struct {
ID string `json:"id"` ID string `json:"id"`
Started time.Time `json:"start_time"` Started time.Time `json:"start_time"`
@ -42,15 +40,15 @@ type EventMetric struct {
Rate15 float64 `json:"rate_15" wire:"unsafe"` Rate15 float64 `json:"rate_15" wire:"unsafe"`
RateMean float64 `json:"rate_mean" wire:"unsafe"` RateMean float64 `json:"rate_mean" wire:"unsafe"`
// so the event can have effects in the event-meter's consumer.
// runs in a go routine
// so the event can have effects in the eventmeter's consumer. runs in a go
// routine.
callback EventCallbackFunc callback EventCallbackFunc
} }
func (metric *EventMetric) Copy() *EventMetric { func (metric *EventMetric) Copy() *EventMetric {
metric2 := *metric
metric2.meter = metric.meter.Snapshot()
return &metric2
metricCopy := *metric
metricCopy.meter = metric.meter.Snapshot()
return &metricCopy
} }
// called on GetMetric // called on GetMetric
@ -63,35 +61,32 @@ func (metric *EventMetric) fillMetric() *EventMetric {
return metric return metric
} }
//------------------------------------------------------
// Websocket client and event meter for many events
const maxPingsPerPong = 30 // if we haven't received a pong in this many attempted pings we kill the conn
// EventCallbackFunc is a closure to enable side effects from receiving an
// event.
type EventCallbackFunc func(em *EventMetric, data interface{})
// Get the eventID and data out of the raw json received over the go-rpc websocket
// EventUnmarshalFunc is a closure to get the eventType and data out of the raw
// JSON received over the RPC WebSocket.
type EventUnmarshalFunc func(b json.RawMessage) (string, events.EventData, error) type EventUnmarshalFunc func(b json.RawMessage) (string, events.EventData, error)
// Closure to enable side effects from receiving a pong
// LatencyCallbackFunc is a closure to enable side effects from receiving a latency.
type LatencyCallbackFunc func(meanLatencyNanoSeconds float64) type LatencyCallbackFunc func(meanLatencyNanoSeconds float64)
// Closure to notify consumer that the connection died
// DisconnectCallbackFunc is a closure to notify a consumer that the connection
// has died.
type DisconnectCallbackFunc func() type DisconnectCallbackFunc func()
// Each node gets an event meter to track events for that node
// EventMeter tracks events, reports latency and disconnects.
type EventMeter struct { type EventMeter struct {
wsc *client.WSClient wsc *client.WSClient
mtx sync.Mutex mtx sync.Mutex
events map[string]*EventMetric events map[string]*EventMetric
// to record ws latency
timer metrics.Timer
lastPing time.Time
receivedPong bool
unmarshalEvent EventUnmarshalFunc
latencyCallback LatencyCallbackFunc latencyCallback LatencyCallbackFunc
disconnectCallback DisconnectCallbackFunc disconnectCallback DisconnectCallbackFunc
unmarshalEvent EventUnmarshalFunc
subscribed bool
quit chan struct{} quit chan struct{}
@ -99,54 +94,44 @@ type EventMeter struct {
} }
func NewEventMeter(addr string, unmarshalEvent EventUnmarshalFunc) *EventMeter { func NewEventMeter(addr string, unmarshalEvent EventUnmarshalFunc) *EventMeter {
em := &EventMeter{
wsc: client.NewWSClient(addr, "/websocket"),
return &EventMeter{
wsc: client.NewWSClient(addr, "/websocket", client.PingPeriod(1*time.Second)),
events: make(map[string]*EventMetric), events: make(map[string]*EventMetric),
timer: metrics.NewTimer(),
receivedPong: true,
unmarshalEvent: unmarshalEvent, unmarshalEvent: unmarshalEvent,
logger: log.NewNopLogger(), logger: log.NewNopLogger(),
} }
return em
} }
// SetLogger lets you set your own logger
// SetLogger lets you set your own logger.
func (em *EventMeter) SetLogger(l log.Logger) { func (em *EventMeter) SetLogger(l log.Logger) {
em.logger = l em.logger = l
em.wsc.SetLogger(l.With("module", "rpcclient"))
} }
// String returns a string representation of event meter.
func (em *EventMeter) String() string { func (em *EventMeter) String() string {
return em.wsc.Address return em.wsc.Address
} }
// Start boots up event meter.
func (em *EventMeter) Start() error { func (em *EventMeter) Start() error {
if _, err := em.wsc.Reset(); err != nil {
return err
}
if _, err := em.wsc.Start(); err != nil { if _, err := em.wsc.Start(); err != nil {
return err return err
} }
em.wsc.Conn.SetPongHandler(func(m string) error {
// NOTE: https://github.com/gorilla/websocket/issues/97
em.mtx.Lock()
defer em.mtx.Unlock()
em.receivedPong = true
em.timer.UpdateSince(em.lastPing)
if em.latencyCallback != nil {
go em.latencyCallback(em.timer.Mean())
}
return nil
})
em.quit = make(chan struct{}) em.quit = make(chan struct{})
go em.receiveRoutine() go em.receiveRoutine()
go em.disconnectRoutine()
return em.resubscribe()
err := em.subscribe()
if err != nil {
return err
}
em.subscribed = true
return nil
} }
// Stop stops the EventMeter.
// Stop stops event meter.
func (em *EventMeter) Stop() { func (em *EventMeter) Stop() {
close(em.quit) close(em.quit)
@ -155,88 +140,70 @@ func (em *EventMeter) Stop() {
} }
} }
// StopAndCallDisconnectCallback stops the EventMeter and calls
// disconnectCallback if present.
func (em *EventMeter) StopAndCallDisconnectCallback() {
if em.wsc.IsRunning() {
em.wsc.Stop()
}
// Subscribe for the given event type. Callback function will be called upon
// receiving an event.
func (em *EventMeter) Subscribe(eventType string, cb EventCallbackFunc) error {
em.mtx.Lock() em.mtx.Lock()
defer em.mtx.Unlock() defer em.mtx.Unlock()
if em.disconnectCallback != nil {
go em.disconnectCallback()
}
}
func (em *EventMeter) Subscribe(eventID string, cb EventCallbackFunc) error {
em.mtx.Lock()
defer em.mtx.Unlock()
if _, ok := em.events[eventID]; ok {
if _, ok := em.events[eventType]; ok {
return fmt.Errorf("subscribtion already exists") return fmt.Errorf("subscribtion already exists")
} }
if err := em.wsc.Subscribe(eventID); err != nil {
if err := em.wsc.Subscribe(context.TODO(), eventType); err != nil {
return err return err
} }
metric := &EventMetric{ metric := &EventMetric{
ID: eventID,
Started: time.Now(),
MinDuration: 1 << 62,
meter: metrics.NewMeter(),
callback: cb,
meter: metrics.NewMeter(),
callback: cb,
} }
em.events[eventID] = metric
em.events[eventType] = metric
return nil return nil
} }
func (em *EventMeter) Unsubscribe(eventID string) error {
// Unsubscribe from the given event type.
func (em *EventMeter) Unsubscribe(eventType string) error {
em.mtx.Lock() em.mtx.Lock()
defer em.mtx.Unlock() defer em.mtx.Unlock()
if err := em.wsc.Unsubscribe(eventID); err != nil {
if err := em.wsc.Unsubscribe(context.TODO(), eventType); err != nil {
return err return err
} }
// XXX: should we persist or save this info first? // XXX: should we persist or save this info first?
delete(em.events, eventID)
delete(em.events, eventType)
return nil return nil
} }
// Fill in the latest data for an event and return a copy
func (em *EventMeter) GetMetric(eventID string) (*EventMetric, error) {
// GetMetric fills in the latest data for an event and return a copy.
func (em *EventMeter) GetMetric(eventType string) (*EventMetric, error) {
em.mtx.Lock() em.mtx.Lock()
defer em.mtx.Unlock() defer em.mtx.Unlock()
metric, ok := em.events[eventID]
metric, ok := em.events[eventType]
if !ok { if !ok {
return nil, fmt.Errorf("Unknown event %s", eventID)
return nil, fmt.Errorf("unknown event: %s", eventType)
} }
return metric.fillMetric().Copy(), nil return metric.fillMetric().Copy(), nil
} }
// Return the average latency over the websocket
func (em *EventMeter) Latency() float64 {
em.mtx.Lock()
defer em.mtx.Unlock()
return em.timer.Mean()
}
// RegisterLatencyCallback allows you to set latency callback.
func (em *EventMeter) RegisterLatencyCallback(f LatencyCallbackFunc) { func (em *EventMeter) RegisterLatencyCallback(f LatencyCallbackFunc) {
em.mtx.Lock() em.mtx.Lock()
defer em.mtx.Unlock() defer em.mtx.Unlock()
em.latencyCallback = f em.latencyCallback = f
} }
// RegisterDisconnectCallback allows you to set disconnect callback.
func (em *EventMeter) RegisterDisconnectCallback(f DisconnectCallbackFunc) { func (em *EventMeter) RegisterDisconnectCallback(f DisconnectCallbackFunc) {
em.mtx.Lock() em.mtx.Lock()
defer em.mtx.Unlock() defer em.mtx.Unlock()
em.disconnectCallback = f em.disconnectCallback = f
} }
//------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
// Private
func (em *EventMeter) resubscribe() error {
for eventID, _ := range em.events {
if err := em.wsc.Subscribe(eventID); err != nil {
func (em *EventMeter) subscribe() error {
for eventType, _ := range em.events {
if err := em.wsc.Subscribe(context.TODO(), eventType); err != nil {
return err return err
} }
} }
@ -244,40 +211,31 @@ func (em *EventMeter) resubscribe() error {
} }
func (em *EventMeter) receiveRoutine() { func (em *EventMeter) receiveRoutine() {
pingTime := time.Second * 1
pingTicker := time.NewTicker(pingTime)
pingAttempts := 0 // if this hits maxPingsPerPong we kill the conn
var err error
latencyTicker := time.NewTicker(latencyPeriod)
for { for {
select { select {
case <-pingTicker.C:
if pingAttempts, err = em.pingForLatency(pingAttempts); err != nil {
em.logger.Error("err", errors.Wrap(err, "failed to write ping message on websocket"))
em.StopAndCallDisconnectCallback()
return
} else if pingAttempts >= maxPingsPerPong {
em.logger.Error("err", errors.Errorf("Have not received a pong in %v", time.Duration(pingAttempts)*pingTime))
em.StopAndCallDisconnectCallback()
return
}
case r := <-em.wsc.ResultsCh:
if r == nil {
em.logger.Error("err", errors.New("Expected some event, received nil"))
em.StopAndCallDisconnectCallback()
return
case rawEvent := <-em.wsc.ResultsCh:
if rawEvent == nil {
em.logger.Error("expected some event, got nil")
continue
} }
eventID, data, err := em.unmarshalEvent(r)
eventType, data, err := em.unmarshalEvent(rawEvent)
if err != nil { if err != nil {
em.logger.Error("err", errors.Wrap(err, "failed to unmarshal event"))
em.logger.Error("failed to unmarshal event", "err", err)
continue continue
} }
if eventID != "" {
em.updateMetric(eventID, data)
if eventType != "" { // FIXME how can it be an empty string?
em.updateMetric(eventType, data)
}
case err := <-em.wsc.ErrorsCh:
if err != nil {
em.logger.Error("expected some event, got error", "err", err)
}
case <-latencyTicker.C:
if em.wsc.IsActive() {
em.callLatencyCallback(em.wsc.PingPongLatencyTimer.Mean())
} }
case <-em.wsc.Quit: case <-em.wsc.Quit:
em.logger.Error("err", errors.New("WSClient closed unexpectedly"))
em.StopAndCallDisconnectCallback()
return return
case <-em.quit: case <-em.quit:
return return
@ -285,29 +243,31 @@ func (em *EventMeter) receiveRoutine() {
} }
} }
func (em *EventMeter) pingForLatency(pingAttempts int) (int, error) {
em.mtx.Lock()
defer em.mtx.Unlock()
// ping to record latency
if !em.receivedPong {
return pingAttempts + 1, nil
}
em.lastPing = time.Now()
em.receivedPong = false
err := em.wsc.Conn.WriteMessage(websocket.PingMessage, []byte{})
if err != nil {
return pingAttempts, err
func (em *EventMeter) disconnectRoutine() {
ticker := time.NewTicker(connectionCheckPeriod)
for {
select {
case <-ticker.C:
if em.wsc.IsReconnecting() && em.subscribed { // notify user about disconnect only once
em.callDisconnectCallback()
em.subscribed = false
} else if !em.wsc.IsReconnecting() && !em.subscribed { // resubscribe
em.subscribe()
em.subscribed = true
}
case <-em.wsc.Quit:
return
case <-em.quit:
return
}
} }
return 0, nil
} }
func (em *EventMeter) updateMetric(eventID string, data events.EventData) {
func (em *EventMeter) updateMetric(eventType string, data events.EventData) {
em.mtx.Lock() em.mtx.Lock()
defer em.mtx.Unlock() defer em.mtx.Unlock()
metric, ok := em.events[eventID]
metric, ok := em.events[eventType]
if !ok { if !ok {
// we already unsubscribed, or got an unexpected event // we already unsubscribed, or got an unexpected event
return return
@ -328,3 +288,19 @@ func (em *EventMeter) updateMetric(eventID string, data events.EventData) {
go metric.callback(metric.Copy(), data) go metric.callback(metric.Copy(), data)
} }
} }
func (em *EventMeter) callDisconnectCallback() {
em.mtx.Lock()
if em.disconnectCallback != nil {
go em.disconnectCallback()
}
em.mtx.Unlock()
}
func (em *EventMeter) callLatencyCallback(meanLatencyNanoSeconds float64) {
em.mtx.Lock()
if em.latencyCallback != nil {
go em.latencyCallback(meanLatencyNanoSeconds)
}
em.mtx.Unlock()
}

+ 14
- 6
tm-monitor/glide.lock View File

@ -1,5 +1,5 @@
hash: 30b649bc544a4ebd2b2a6188ce314cb72e6c28be8f3e57ec22e7cb83fd974814
updated: 2017-07-29T18:47:33.199177142Z
hash: 1a38134bef18f688b42d6d52fcb02682604e8c1c9e308f6e2ce8c4a461c903a9
updated: 2017-10-06T06:57:56.777237539Z
imports: imports:
- name: github.com/btcsuite/btcd - name: github.com/btcsuite/btcd
version: 583684b21bfbde9b5fc4403916fd7c807feb0289 version: 583684b21bfbde9b5fc4403916fd7c807feb0289
@ -13,6 +13,12 @@ imports:
- log/term - log/term
- name: github.com/go-logfmt/logfmt - name: github.com/go-logfmt/logfmt
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
- name: github.com/go-playground/locales
version: 1e5f1161c6416a5ff48840eb8724a394e48cc534
subpackages:
- currency
- name: github.com/go-playground/universal-translator
version: 71201497bace774495daed26a3874fd339e0b538
- name: github.com/go-stack/stack - name: github.com/go-stack/stack
version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82
- name: github.com/golang/protobuf - name: github.com/golang/protobuf
@ -28,7 +34,7 @@ imports:
- name: github.com/rcrowley/go-metrics - name: github.com/rcrowley/go-metrics
version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c
- name: github.com/tendermint/abci - name: github.com/tendermint/abci
version: 864d1f80b36b440bde030a5c18d8ac3aa8c2949d
version: 191c4b6d176169ffc7f9972d490fa362a3b7d940
subpackages: subpackages:
- client - client
- example/dummy - example/dummy
@ -39,13 +45,13 @@ imports:
- edwards25519 - edwards25519
- extra25519 - extra25519
- name: github.com/tendermint/go-crypto - name: github.com/tendermint/go-crypto
version: 95b7c9e09c49b91bfbb71bb63dd514eb55450f16
version: 311e8c1bf00fa5868daad4f8ea56dcad539182c0
- name: github.com/tendermint/go-wire - name: github.com/tendermint/go-wire
version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb
subpackages: subpackages:
- data - data
- name: github.com/tendermint/tendermint - name: github.com/tendermint/tendermint
version: e9b7221292afe25ce956ea85ab83bb5708eb2992
version: 7682ad9a60162dd17fd6f61aeed7049a8635ac78
subpackages: subpackages:
- config - config
- p2p - p2p
@ -56,7 +62,7 @@ imports:
- rpc/lib/types - rpc/lib/types
- types - types
- name: github.com/tendermint/tmlibs - name: github.com/tendermint/tmlibs
version: 2f6f3e6aa70bb19b70a6e73210273fa127041070
version: 7dd6b3d3f8a7a998a79bdd0d8222252b309570f3
subpackages: subpackages:
- common - common
- events - events
@ -97,6 +103,8 @@ imports:
- stats - stats
- tap - tap
- transport - transport
- name: gopkg.in/go-playground/validator.v9
version: a021b2ec9a8a8bb970f3f15bc42617cb520e8a64
testImports: testImports:
- name: github.com/davecgh/go-spew - name: github.com/davecgh/go-spew
version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9


+ 1
- 1
tm-monitor/glide.yaml View File

@ -5,7 +5,7 @@ import:
- package: github.com/rcrowley/go-metrics - package: github.com/rcrowley/go-metrics
- package: github.com/tendermint/go-crypto - package: github.com/tendermint/go-crypto
- package: github.com/tendermint/tendermint - package: github.com/tendermint/tendermint
version: develop
version: v0.11.0
subpackages: subpackages:
- rpc/core/types - rpc/core/types
- rpc/lib/client - rpc/lib/client


+ 2
- 2
tm-monitor/main.go View File

@ -11,7 +11,7 @@ import (
monitor "github.com/tendermint/tools/tm-monitor/monitor" monitor "github.com/tendermint/tools/tm-monitor/monitor"
) )
var version = "0.2.1"
var version = "0.3.0"
var logger = log.NewNopLogger() var logger = log.NewNopLogger()
@ -47,7 +47,7 @@ Examples:
} }
if noton { if noton {
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "tm-monitor")
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout))
} }
m := startMonitor(flag.Arg(0)) m := startMonitor(flag.Arg(0))


tm-monitor/mock/mock.go → tm-monitor/mock/eventmeter.go View File


+ 4
- 15
tm-monitor/monitor/node.go View File

@ -129,7 +129,7 @@ func newBlockCallback(n *Node) em.EventCallbackFunc {
block := data.(tmtypes.TMEventData).Unwrap().(tmtypes.EventDataNewBlockHeader).Header block := data.(tmtypes.TMEventData).Unwrap().(tmtypes.EventDataNewBlockHeader).Header
n.Height = uint64(block.Height) n.Height = uint64(block.Height)
n.logger.Info("event", "new block", "height", block.Height, "numTxs", block.NumTxs)
n.logger.Info("new block", "height", block.Height, "numTxs", block.NumTxs)
if n.blockCh != nil { if n.blockCh != nil {
n.blockCh <- *block n.blockCh <- *block
@ -141,7 +141,7 @@ func newBlockCallback(n *Node) em.EventCallbackFunc {
func latencyCallback(n *Node) em.LatencyCallbackFunc { func latencyCallback(n *Node) em.LatencyCallbackFunc {
return func(latency float64) { return func(latency float64) {
n.BlockLatency = latency / 1000000.0 // ns to ms n.BlockLatency = latency / 1000000.0 // ns to ms
n.logger.Info("event", "new block latency", "latency", n.BlockLatency)
n.logger.Info("new block latency", "latency", n.BlockLatency)
if n.blockLatencyCh != nil { if n.blockLatencyCh != nil {
n.blockLatencyCh <- latency n.blockLatencyCh <- latency
@ -158,17 +158,6 @@ func disconnectCallback(n *Node) em.DisconnectCallbackFunc {
if n.disconnectCh != nil { if n.disconnectCh != nil {
n.disconnectCh <- true n.disconnectCh <- true
} }
if err := n.RestartEventMeterBackoff(); err != nil {
n.logger.Info("err", errors.Wrap(err, "restart failed"))
} else {
n.Online = true
n.logger.Info("status", "online")
if n.disconnectCh != nil {
n.disconnectCh <- false
}
}
} }
} }
@ -180,7 +169,7 @@ func (n *Node) RestartEventMeterBackoff() error {
time.Sleep(d * time.Second) time.Sleep(d * time.Second)
if err := n.em.Start(); err != nil { if err := n.em.Start(); err != nil {
n.logger.Info("err", errors.Wrap(err, "restart failed"))
n.logger.Info("restart failed", "err", err)
} else { } else {
// TODO: authenticate pubkey // TODO: authenticate pubkey
return nil return nil
@ -231,7 +220,7 @@ func (n *Node) checkIsValidator() {
} }
} }
} else { } else {
n.logger.Info("err", errors.Wrap(err, "check is validator failed"))
n.logger.Info("check is validator failed", "err", err)
} }
} }


+ 1
- 4
tm-monitor/monitor/node_test.go View File

@ -68,10 +68,7 @@ func TestNodeConnectionLost(t *testing.T) {
emMock.Call("disconnectCallback") emMock.Call("disconnectCallback")
assert.Equal(true, <-disconnectCh) assert.Equal(true, <-disconnectCh)
assert.Equal(false, <-disconnectCh)
// we're back in a race
assert.Equal(true, n.Online)
assert.Equal(false, n.Online)
} }
func TestNumValidators(t *testing.T) { func TestNumValidators(t *testing.T) {


+ 1
- 2
tm-monitor/rpc.go View File

@ -12,9 +12,8 @@ import (
func startRPC(listenAddr string, m *monitor.Monitor, logger log.Logger) { func startRPC(listenAddr string, m *monitor.Monitor, logger log.Logger) {
routes := routes(m) routes := routes(m)
// serve http and ws
mux := http.NewServeMux() mux := http.NewServeMux()
wm := rpc.NewWebsocketManager(routes, nil) // TODO: evsw
wm := rpc.NewWebsocketManager(routes, nil)
mux.HandleFunc("/websocket", wm.WebsocketHandler) mux.HandleFunc("/websocket", wm.WebsocketHandler)
rpc.RegisterRPCFuncs(mux, routes, logger) rpc.RegisterRPCFuncs(mux, routes, logger)
if _, err := rpc.StartHTTPServer(listenAddr, mux, logger); err != nil { if _, err := rpc.StartHTTPServer(listenAddr, mux, logger); err != nil {


Loading…
Cancel
Save