|
|
@@ -658,11 +658,16 @@ func (s *Session) connect(host *HostInfo, errorHandler ConnErrorHandler) (*Conn,
|
|
|
return s.dial(host, s.connCfg, errorHandler)
|
|
|
}
|
|
|
|
|
|
-type queryMetrics struct {
|
|
|
+type hostMetrics struct {
|
|
|
Attempts int
|
|
|
TotalLatency int64
|
|
|
}
|
|
|
|
|
|
+type queryMetrics struct {
|
|
|
+ l sync.RWMutex
|
|
|
+ m map[string]*hostMetrics
|
|
|
+}
|
|
|
+
|
|
|
// Query represents a CQL statement that can be executed.
|
|
|
type Query struct {
|
|
|
stmt string
|
|
|
@@ -677,6 +682,7 @@ type Query struct {
|
|
|
observer QueryObserver
|
|
|
session *Session
|
|
|
rt RetryPolicy
|
|
|
+ spec SpeculativeExecutionPolicy
|
|
|
binding func(q *QueryInfo) ([]interface{}, error)
|
|
|
serialCons SerialConsistency
|
|
|
defaultTimestamp bool
|
|
|
@@ -685,8 +691,8 @@ type Query struct {
|
|
|
context context.Context
|
|
|
cancelQuery func()
|
|
|
idempotent bool
|
|
|
- metrics map[string]*queryMetrics
|
|
|
customPayload map[string][]byte
|
|
|
+ metrics *queryMetrics
|
|
|
|
|
|
disableAutoPage bool
|
|
|
}
|
|
|
@@ -704,23 +710,26 @@ func (q *Query) defaultsFromSession() {
|
|
|
q.serialCons = s.cfg.SerialConsistency
|
|
|
q.defaultTimestamp = s.cfg.DefaultTimestamp
|
|
|
q.idempotent = s.cfg.DefaultIdempotence
|
|
|
- q.metrics = make(map[string]*queryMetrics)
|
|
|
+ q.metrics = &queryMetrics{m: make(map[string]*hostMetrics)}
|
|
|
|
|
|
// Initiate an empty context with a cancel call
|
|
|
q.WithContext(context.Background())
|
|
|
|
|
|
+ q.spec = &NonSpeculativeExecution{}
|
|
|
s.mu.RUnlock()
|
|
|
}
|
|
|
|
|
|
-func (q *Query) getHostMetrics(host *HostInfo) *queryMetrics {
|
|
|
- hostMetrics, exists := q.metrics[host.ConnectAddress().String()]
|
|
|
+func (q *Query) getHostMetrics(host *HostInfo) *hostMetrics {
|
|
|
+ q.metrics.l.Lock()
|
|
|
+ metrics, exists := q.metrics.m[host.ConnectAddress().String()]
|
|
|
if !exists {
|
|
|
// if the host is not in the map, it means it's been accessed for the first time
|
|
|
- hostMetrics = &queryMetrics{Attempts: 0, TotalLatency: 0}
|
|
|
- q.metrics[host.ConnectAddress().String()] = hostMetrics
|
|
|
+ metrics = &hostMetrics{}
|
|
|
+ q.metrics.m[host.ConnectAddress().String()] = metrics
|
|
|
}
|
|
|
+ q.metrics.l.Unlock()
|
|
|
|
|
|
- return hostMetrics
|
|
|
+ return metrics
|
|
|
}
|
|
|
|
|
|
// Statement returns the statement that was used to generate this query.
|
|
|
@@ -735,27 +744,45 @@ func (q Query) String() string {
|
|
|
|
|
|
//Attempts returns the number of times the query was executed.
|
|
|
func (q *Query) Attempts() int {
|
|
|
- attempts := 0
|
|
|
- for _, metric := range q.metrics {
|
|
|
+ q.metrics.l.Lock()
|
|
|
+ var attempts int
|
|
|
+ for _, metric := range q.metrics.m {
|
|
|
attempts += metric.Attempts
|
|
|
}
|
|
|
+ q.metrics.l.Unlock()
|
|
|
return attempts
|
|
|
}
|
|
|
|
|
|
+func (q *Query) AddAttempts(i int, host *HostInfo) {
|
|
|
+ hostMetric := q.getHostMetrics(host)
|
|
|
+ q.metrics.l.Lock()
|
|
|
+ hostMetric.Attempts += i
|
|
|
+ q.metrics.l.Unlock()
|
|
|
+}
|
|
|
+
|
|
|
//Latency returns the average amount of nanoseconds per attempt of the query.
|
|
|
func (q *Query) Latency() int64 {
|
|
|
+ q.metrics.l.Lock()
|
|
|
var attempts int
|
|
|
var latency int64
|
|
|
- for _, metric := range q.metrics {
|
|
|
+ for _, metric := range q.metrics.m {
|
|
|
attempts += metric.Attempts
|
|
|
latency += metric.TotalLatency
|
|
|
}
|
|
|
+ q.metrics.l.Unlock()
|
|
|
if attempts > 0 {
|
|
|
return latency / int64(attempts)
|
|
|
}
|
|
|
return 0
|
|
|
}
|
|
|
|
|
|
+func (q *Query) AddLatency(l int64, host *HostInfo) {
|
|
|
+ hostMetric := q.getHostMetrics(host)
|
|
|
+ q.metrics.l.Lock()
|
|
|
+ hostMetric.TotalLatency += l
|
|
|
+ q.metrics.l.Unlock()
|
|
|
+}
|
|
|
+
|
|
|
// Consistency sets the consistency level for this query. If no consistency
|
|
|
// level have been set, the default consistency level of the cluster
|
|
|
// is used.
|
|
|
@@ -781,6 +808,10 @@ func (q *Query) CustomPayload(customPayload map[string][]byte) *Query {
|
|
|
return q
|
|
|
}
|
|
|
|
|
|
+func (q *Query) GetContext() context.Context {
|
|
|
+ return q.context
|
|
|
+}
|
|
|
+
|
|
|
// Trace enables tracing of this query. Look at the documentation of the
|
|
|
// Tracer interface to learn more about tracing.
|
|
|
func (q *Query) Trace(trace Tracer) *Query {
|
|
|
@@ -851,9 +882,8 @@ func (q *Query) execute(conn *Conn) *Iter {
|
|
|
}
|
|
|
|
|
|
func (q *Query) attempt(keyspace string, end, start time.Time, iter *Iter, host *HostInfo) {
|
|
|
- hostMetrics := q.getHostMetrics(host)
|
|
|
- hostMetrics.Attempts++
|
|
|
- hostMetrics.TotalLatency += end.Sub(start).Nanoseconds()
|
|
|
+ q.AddAttempts(1, host)
|
|
|
+ q.AddLatency(end.Sub(start).Nanoseconds(), host)
|
|
|
|
|
|
if q.observer != nil {
|
|
|
q.observer.ObserveQuery(q.context, ObservedQuery{
|
|
|
@@ -863,7 +893,7 @@ func (q *Query) attempt(keyspace string, end, start time.Time, iter *Iter, host
|
|
|
End: end,
|
|
|
Rows: iter.numRows,
|
|
|
Host: host,
|
|
|
- Metrics: hostMetrics,
|
|
|
+ Metrics: q.getHostMetrics(host),
|
|
|
Err: iter.err,
|
|
|
})
|
|
|
}
|
|
|
@@ -983,6 +1013,17 @@ func (q *Query) RetryPolicy(r RetryPolicy) *Query {
|
|
|
return q
|
|
|
}
|
|
|
|
|
|
+// SetSpeculativeExecutionPolicy sets the execution policy
|
|
|
+func (q *Query) SetSpeculativeExecutionPolicy(sp SpeculativeExecutionPolicy) *Query {
|
|
|
+ q.spec = sp
|
|
|
+ return q
|
|
|
+}
|
|
|
+
|
|
|
+// speculativeExecutionPolicy fetches the policy
|
|
|
+func (q *Query) speculativeExecutionPolicy() SpeculativeExecutionPolicy {
|
|
|
+ return q.spec
|
|
|
+}
|
|
|
+
|
|
|
func (q *Query) IsIdempotent() bool {
|
|
|
return q.idempotent
|
|
|
}
|
|
|
@@ -1431,6 +1472,7 @@ type Batch struct {
|
|
|
Cons Consistency
|
|
|
CustomPayload map[string][]byte
|
|
|
rt RetryPolicy
|
|
|
+ spec SpeculativeExecutionPolicy
|
|
|
observer BatchObserver
|
|
|
serialCons SerialConsistency
|
|
|
defaultTimestamp bool
|
|
|
@@ -1438,14 +1480,14 @@ type Batch struct {
|
|
|
context context.Context
|
|
|
cancelBatch func()
|
|
|
keyspace string
|
|
|
- metrics map[string]*queryMetrics
|
|
|
+ metrics *queryMetrics
|
|
|
}
|
|
|
|
|
|
// NewBatch creates a new batch operation without defaults from the cluster
|
|
|
//
|
|
|
// Deprecated: use session.NewBatch instead
|
|
|
func NewBatch(typ BatchType) *Batch {
|
|
|
- return &Batch{Type: typ, metrics: make(map[string]*queryMetrics)}
|
|
|
+ return &Batch{Type: typ, metrics: &queryMetrics{m: make(map[string]*hostMetrics)}}
|
|
|
}
|
|
|
|
|
|
// NewBatch creates a new batch operation using defaults defined in the cluster
|
|
|
@@ -1459,7 +1501,8 @@ func (s *Session) NewBatch(typ BatchType) *Batch {
|
|
|
Cons: s.cons,
|
|
|
defaultTimestamp: s.cfg.DefaultTimestamp,
|
|
|
keyspace: s.cfg.Keyspace,
|
|
|
- metrics: make(map[string]*queryMetrics),
|
|
|
+ metrics: &queryMetrics{m: make(map[string]*hostMetrics)},
|
|
|
+ spec: &NonSpeculativeExecution{},
|
|
|
}
|
|
|
|
|
|
// Initiate an empty context with a cancel call
|
|
|
@@ -1469,15 +1512,17 @@ func (s *Session) NewBatch(typ BatchType) *Batch {
|
|
|
return batch
|
|
|
}
|
|
|
|
|
|
-func (b *Batch) getHostMetrics(host *HostInfo) *queryMetrics {
|
|
|
- hostMetrics, exists := b.metrics[host.ConnectAddress().String()]
|
|
|
+func (b *Batch) getHostMetrics(host *HostInfo) *hostMetrics {
|
|
|
+ b.metrics.l.Lock()
|
|
|
+ metrics, exists := b.metrics.m[host.ConnectAddress().String()]
|
|
|
if !exists {
|
|
|
// if the host is not in the map, it means it's been accessed for the first time
|
|
|
- hostMetrics = &queryMetrics{Attempts: 0, TotalLatency: 0}
|
|
|
- b.metrics[host.ConnectAddress().String()] = hostMetrics
|
|
|
+ metrics = &hostMetrics{}
|
|
|
+ b.metrics.m[host.ConnectAddress().String()] = metrics
|
|
|
}
|
|
|
+ b.metrics.l.Unlock()
|
|
|
|
|
|
- return hostMetrics
|
|
|
+ return metrics
|
|
|
}
|
|
|
|
|
|
// Observer enables batch-level observer on this batch.
|
|
|
@@ -1493,18 +1538,33 @@ func (b *Batch) Keyspace() string {
|
|
|
|
|
|
// Attempts returns the number of attempts made to execute the batch.
|
|
|
func (b *Batch) Attempts() int {
|
|
|
- attempts := 0
|
|
|
- for _, metric := range b.metrics {
|
|
|
+ b.metrics.l.Lock()
|
|
|
+ defer b.metrics.l.Unlock()
|
|
|
+
|
|
|
+ var attempts int
|
|
|
+ for _, metric := range b.metrics.m {
|
|
|
attempts += metric.Attempts
|
|
|
}
|
|
|
return attempts
|
|
|
}
|
|
|
|
|
|
+func (b *Batch) AddAttempts(i int, host *HostInfo) {
|
|
|
+ hostMetric := b.getHostMetrics(host)
|
|
|
+ b.metrics.l.Lock()
|
|
|
+ hostMetric.Attempts += i
|
|
|
+ b.metrics.l.Unlock()
|
|
|
+}
|
|
|
+
|
|
|
//Latency returns the average number of nanoseconds to execute a single attempt of the batch.
|
|
|
func (b *Batch) Latency() int64 {
|
|
|
- attempts := 0
|
|
|
- var latency int64 = 0
|
|
|
- for _, metric := range b.metrics {
|
|
|
+ b.metrics.l.Lock()
|
|
|
+ defer b.metrics.l.Unlock()
|
|
|
+
|
|
|
+ var (
|
|
|
+ attempts int
|
|
|
+ latency int64
|
|
|
+ )
|
|
|
+ for _, metric := range b.metrics.m {
|
|
|
attempts += metric.Attempts
|
|
|
latency += metric.TotalLatency
|
|
|
}
|
|
|
@@ -1514,6 +1574,13 @@ func (b *Batch) Latency() int64 {
|
|
|
return 0
|
|
|
}
|
|
|
|
|
|
+func (b *Batch) AddLatency(l int64, host *HostInfo) {
|
|
|
+ hostMetric := b.getHostMetrics(host)
|
|
|
+ b.metrics.l.Lock()
|
|
|
+ hostMetric.TotalLatency += l
|
|
|
+ b.metrics.l.Unlock()
|
|
|
+}
|
|
|
+
|
|
|
// GetConsistency returns the currently configured consistency level for the batch
|
|
|
// operation.
|
|
|
func (b *Batch) GetConsistency() Consistency {
|
|
|
@@ -1526,6 +1593,28 @@ func (b *Batch) SetConsistency(c Consistency) {
|
|
|
b.Cons = c
|
|
|
}
|
|
|
|
|
|
+func (b *Batch) GetContext() context.Context {
|
|
|
+ return b.context
|
|
|
+}
|
|
|
+
|
|
|
+func (b *Batch) IsIdempotent() bool {
|
|
|
+ for _, entry := range b.Entries {
|
|
|
+ if !entry.Idempotent {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+func (b *Batch) speculativeExecutionPolicy() SpeculativeExecutionPolicy {
|
|
|
+ return b.spec
|
|
|
+}
|
|
|
+
|
|
|
+func (b *Batch) SpeculativeExecutionPolicy(sp SpeculativeExecutionPolicy) *Batch {
|
|
|
+ b.spec = sp
|
|
|
+ return b
|
|
|
+}
|
|
|
+
|
|
|
// Query adds the query to the batch operation
|
|
|
func (b *Batch) Query(stmt string, args ...interface{}) {
|
|
|
b.Entries = append(b.Entries, BatchEntry{Stmt: stmt, Args: args})
|
|
|
@@ -1601,9 +1690,8 @@ func (b *Batch) WithTimestamp(timestamp int64) *Batch {
|
|
|
}
|
|
|
|
|
|
func (b *Batch) attempt(keyspace string, end, start time.Time, iter *Iter, host *HostInfo) {
|
|
|
- hostMetrics := b.getHostMetrics(host)
|
|
|
- hostMetrics.Attempts++
|
|
|
- hostMetrics.TotalLatency += end.Sub(start).Nanoseconds()
|
|
|
+ b.AddAttempts(1, host)
|
|
|
+ b.AddLatency(end.Sub(start).Nanoseconds(), host)
|
|
|
|
|
|
if b.observer == nil {
|
|
|
return
|
|
|
@@ -1621,7 +1709,7 @@ func (b *Batch) attempt(keyspace string, end, start time.Time, iter *Iter, host
|
|
|
End: end,
|
|
|
// Rows not used in batch observations // TODO - might be able to support it when using BatchCAS
|
|
|
Host: host,
|
|
|
- Metrics: hostMetrics,
|
|
|
+ Metrics: b.getHostMetrics(host),
|
|
|
Err: iter.err,
|
|
|
})
|
|
|
}
|
|
|
@@ -1640,9 +1728,10 @@ const (
|
|
|
)
|
|
|
|
|
|
type BatchEntry struct {
|
|
|
- Stmt string
|
|
|
- Args []interface{}
|
|
|
- binding func(q *QueryInfo) ([]interface{}, error)
|
|
|
+ Stmt string
|
|
|
+ Args []interface{}
|
|
|
+ Idempotent bool
|
|
|
+ binding func(q *QueryInfo) ([]interface{}, error)
|
|
|
}
|
|
|
|
|
|
type ColumnInfo struct {
|
|
|
@@ -1775,7 +1864,7 @@ type ObservedQuery struct {
|
|
|
Host *HostInfo
|
|
|
|
|
|
// The metrics per this host
|
|
|
- Metrics *queryMetrics
|
|
|
+ Metrics *hostMetrics
|
|
|
|
|
|
// Err is the error in the query.
|
|
|
// It only tracks network errors or errors of bad cassandra syntax, in particular selects with no match return nil error
|
|
|
@@ -1807,7 +1896,7 @@ type ObservedBatch struct {
|
|
|
Err error
|
|
|
|
|
|
// The metrics per this host
|
|
|
- Metrics *queryMetrics
|
|
|
+ Metrics *hostMetrics
|
|
|
}
|
|
|
|
|
|
// BatchObserver is the interface implemented by batch observers / stat collectors.
|