func (c *Client) Stats() (map[string]interface{}, error) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	stats := map[string]interface{}{
		"timeout":         c.timeout,
		"local_node_addr": c.localNodeAddr,
	}
	if (len(c.pools)) == 0 {
		return stats, nil
	}
	poolStats := make(map[string]interface{}, len(c.pools))
	for k, v := range c.pools {
		s, err := v.Stats()
		if err != nil {
			return nil, err
		}
		poolStats[k] = s
	}
	stats["conn_pool_stats"] = poolStats
	return stats, nil
}
Stats returns stats on the Client instance
package cluster
import (
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"net"
	"sync"
	"time"
	"github.com/golang/protobuf/proto"
	"github.com/rqlite/rqlite/command"
	"github.com/rqlite/rqlite/tcp/pool"
)
const (
	initialPoolSize = 4
	maxPoolCapacity = 64
)
type Client struct {
	dialer  Dialer
	timeout time.Duration
	lMu           sync.RWMutex
	localNodeAddr string
	localServ     *Service
	mu    sync.RWMutex
	pools map[string]pool.Pool
}
func NewClient(dl Dialer) *Client {
	return &Client{
		dialer:  dl,
		timeout: 30 * time.Second,
		pools:   make(map[string]pool.Pool),
	}
}
func (c *Client) SetLocal(nodeAddr string, serv *Service) error {
	c.lMu.Lock()
	defer c.lMu.Unlock()
	adv, err := net.ResolveTCPAddr("tcp", nodeAddr)
	if err != nil {
		return fmt.Errorf("failed to resolve advertise address %s: %s", nodeAddr, err.Error())
	}
	c.localNodeAddr = adv.String()
	c.localServ = serv
	return nil
}
func (c *Client) GetNodeAPIAddr(nodeAddr string, timeout time.Duration) (string, error) {
	c.lMu.RLock()
	defer c.lMu.RUnlock()
	if c.localNodeAddr == nodeAddr && c.localServ != nil {
		stats.Add(numGetNodeAPIRequestLocal, 1)
		return c.localServ.GetNodeAPIURL(), nil
	}
	conn, err := c.dial(nodeAddr, c.timeout)
	if err != nil {
		return "", err
	}
	defer conn.Close()
	command := &Command{
		Type: Command_COMMAND_TYPE_GET_NODE_API_URL,
	}
	p, err := proto.Marshal(command)
	if err != nil {
		return "", fmt.Errorf("command marshal: %s", err)
	}
	b := make([]byte, 4)
	binary.LittleEndian.PutUint16(b[0:], uint16(len(p)))
	if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
		handleConnError(conn)
		return "", err
	}
	_, err = conn.Write(b)
	if err != nil {
		handleConnError(conn)
		return "", fmt.Errorf("write protobuf length: %s", err)
	}
	if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
		handleConnError(conn)
		return "", err
	}
	_, err = conn.Write(p)
	if err != nil {
		handleConnError(conn)
		return "", fmt.Errorf("write protobuf: %s", err)
	}
	if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
		handleConnError(conn)
		return "", err
	}
	_, err = io.ReadFull(conn, b)
	if err != nil {
		return "", err
	}
	sz := binary.LittleEndian.Uint16(b[0:])
	p = make([]byte, sz)
	if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
		handleConnError(conn)
		return "", err
	}
	_, err = io.ReadFull(conn, p)
	if err != nil {
		return "", err
	}
	a := &Address{}
	err = proto.Unmarshal(p, a)
	if err != nil {
		return "", fmt.Errorf("protobuf unmarshal: %s", err)
	}
	return a.Url, nil
}
func (c *Client) Execute(er *command.ExecuteRequest, nodeAddr string, timeout time.Duration) ([]*command.ExecuteResult, error) {
	conn, err := c.dial(nodeAddr, c.timeout)
	if err != nil {
		return nil, err
	}
	defer conn.Close()
	command := &Command{
		Type: Command_COMMAND_TYPE_EXECUTE,
		Request: &Command_ExecuteRequest{
			ExecuteRequest: er,
		},
	}
	p, err := proto.Marshal(command)
	if err != nil {
		return nil, fmt.Errorf("command marshal: %s", err)
	}
	b := make([]byte, 4)
	binary.LittleEndian.PutUint16(b[0:], uint16(len(p)))
	if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
		handleConnError(conn)
		return nil, err
	}
	_, err = conn.Write(b)
	if err != nil {
		handleConnError(conn)
		return nil, err
	}
	if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
		handleConnError(conn)
		return nil, err
	}
	_, err = conn.Write(p)
	if err != nil {
		handleConnError(conn)
		return nil, err
	}
	if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
		handleConnError(conn)
		return nil, err
	}
	_, err = io.ReadFull(conn, b)
	if err != nil {
		return nil, err
	}
	sz := binary.LittleEndian.Uint32(b[0:])
	p = make([]byte, sz)
	if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
		handleConnError(conn)
		return nil, err
	}
	_, err = io.ReadFull(conn, p)
	if err != nil {
		return nil, err
	}
	a := &CommandExecuteResponse{}
	err = proto.Unmarshal(p, a)
	if err != nil {
		return nil, err
	}
	if a.Error != "" {
		return nil, errors.New(a.Error)
	}
	return a.Results, nil
}
func (c *Client) Query(qr *command.QueryRequest, nodeAddr string, timeout time.Duration) ([]*command.QueryRows, error) {
	conn, err := c.dial(nodeAddr, c.timeout)
	if err != nil {
		return nil, err
	}
	defer conn.Close()
	command := &Command{
		Type: Command_COMMAND_TYPE_QUERY,
		Request: &Command_QueryRequest{
			QueryRequest: qr,
		},
	}
	p, err := proto.Marshal(command)
	if err != nil {
		return nil, fmt.Errorf("command marshal: %s", err)
	}
	b := make([]byte, 4)
	binary.LittleEndian.PutUint16(b[0:], uint16(len(p)))
	if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
		handleConnError(conn)
		return nil, err
	}
	_, err = conn.Write(b)
	if err != nil {
		handleConnError(conn)
		return nil, err
	}
	if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
		handleConnError(conn)
		return nil, err
	}
	_, err = conn.Write(p)
	if err != nil {
		handleConnError(conn)
		return nil, err
	}
	if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
		handleConnError(conn)
		return nil, err
	}
	_, err = io.ReadFull(conn, b)
	if err != nil {
		return nil, err
	}
	sz := binary.LittleEndian.Uint32(b[0:])
	p = make([]byte, sz)
	_, err = io.ReadFull(conn, p)
	if err != nil {
		return nil, err
	}
	a := &CommandQueryResponse{}
	err = proto.Unmarshal(p, a)
	if err != nil {
		return nil, err
	}
	if a.Error != "" {
		return nil, errors.New(a.Error)
	}
	return a.Rows, nil
}