|
@@ -37,6 +37,8 @@ type Session struct {
|
|
|
routingKeyInfoCache routingKeyInfoLRU
|
|
routingKeyInfoCache routingKeyInfoLRU
|
|
|
schemaDescriber *schemaDescriber
|
|
schemaDescriber *schemaDescriber
|
|
|
trace Tracer
|
|
trace Tracer
|
|
|
|
|
+ queryObserver QueryObserver
|
|
|
|
|
+ batchObserver BatchObserver
|
|
|
hostSource *ringDescriber
|
|
hostSource *ringDescriber
|
|
|
stmtsLRU *preparedLRU
|
|
stmtsLRU *preparedLRU
|
|
|
|
|
|
|
@@ -134,6 +136,9 @@ func NewSession(cfg ClusterConfig) (*Session, error) {
|
|
|
policy: cfg.PoolConfig.HostSelectionPolicy,
|
|
policy: cfg.PoolConfig.HostSelectionPolicy,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ s.queryObserver = cfg.QueryObserver
|
|
|
|
|
+ s.batchObserver = cfg.BatchObserver
|
|
|
|
|
+
|
|
|
//Check the TLS Config before trying to connect to anything external
|
|
//Check the TLS Config before trying to connect to anything external
|
|
|
connCfg, err := connConfig(&s.cfg)
|
|
connCfg, err := connConfig(&s.cfg)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -314,6 +319,7 @@ func (s *Session) Query(stmt string, values ...interface{}) *Query {
|
|
|
qry.session = s
|
|
qry.session = s
|
|
|
qry.pageSize = s.pageSize
|
|
qry.pageSize = s.pageSize
|
|
|
qry.trace = s.trace
|
|
qry.trace = s.trace
|
|
|
|
|
+ qry.observer = s.queryObserver
|
|
|
qry.prefetch = s.prefetch
|
|
qry.prefetch = s.prefetch
|
|
|
qry.rt = s.cfg.RetryPolicy
|
|
qry.rt = s.cfg.RetryPolicy
|
|
|
qry.serialCons = s.cfg.SerialConsistency
|
|
qry.serialCons = s.cfg.SerialConsistency
|
|
@@ -338,7 +344,7 @@ type QueryInfo struct {
|
|
|
func (s *Session) Bind(stmt string, b func(q *QueryInfo) ([]interface{}, error)) *Query {
|
|
func (s *Session) Bind(stmt string, b func(q *QueryInfo) ([]interface{}, error)) *Query {
|
|
|
s.mu.RLock()
|
|
s.mu.RLock()
|
|
|
qry := &Query{stmt: stmt, binding: b, cons: s.cons,
|
|
qry := &Query{stmt: stmt, binding: b, cons: s.cons,
|
|
|
- session: s, pageSize: s.pageSize, trace: s.trace,
|
|
|
|
|
|
|
+ session: s, pageSize: s.pageSize, trace: s.trace, observer: s.queryObserver,
|
|
|
prefetch: s.prefetch, rt: s.cfg.RetryPolicy}
|
|
prefetch: s.prefetch, rt: s.cfg.RetryPolicy}
|
|
|
s.mu.RUnlock()
|
|
s.mu.RUnlock()
|
|
|
return qry
|
|
return qry
|
|
@@ -383,7 +389,7 @@ func (s *Session) Closed() bool {
|
|
|
return closed
|
|
return closed
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (s *Session) executeQuery(qry *Query) *Iter {
|
|
|
|
|
|
|
+func (s *Session) executeQuery(qry *Query) (it *Iter) {
|
|
|
// fail fast
|
|
// fail fast
|
|
|
if s.Closed() {
|
|
if s.Closed() {
|
|
|
return &Iter{err: ErrSessionClosed}
|
|
return &Iter{err: ErrSessionClosed}
|
|
@@ -656,6 +662,7 @@ type Query struct {
|
|
|
pageState []byte
|
|
pageState []byte
|
|
|
prefetch float64
|
|
prefetch float64
|
|
|
trace Tracer
|
|
trace Tracer
|
|
|
|
|
+ observer QueryObserver
|
|
|
session *Session
|
|
session *Session
|
|
|
rt RetryPolicy
|
|
rt RetryPolicy
|
|
|
binding func(q *QueryInfo) ([]interface{}, error)
|
|
binding func(q *QueryInfo) ([]interface{}, error)
|
|
@@ -709,6 +716,13 @@ func (q *Query) Trace(trace Tracer) *Query {
|
|
|
return q
|
|
return q
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Observer enables query-level observer on this query.
|
|
|
|
|
+// The provided observer will be called every time this query is executed.
|
|
|
|
|
+func (q *Query) Observer(observer QueryObserver) *Query {
|
|
|
|
|
+ q.observer = observer
|
|
|
|
|
+ return q
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// PageSize will tell the iterator to fetch the result in pages of size n.
|
|
// PageSize will tell the iterator to fetch the result in pages of size n.
|
|
|
// This is useful for iterating over large result sets, but setting the
|
|
// This is useful for iterating over large result sets, but setting the
|
|
|
// page size too low might decrease the performance. This feature is only
|
|
// page size too low might decrease the performance. This feature is only
|
|
@@ -759,10 +773,21 @@ func (q *Query) execute(conn *Conn) *Iter {
|
|
|
return conn.executeQuery(q)
|
|
return conn.executeQuery(q)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (q *Query) attempt(d time.Duration) {
|
|
|
|
|
|
|
+func (q *Query) attempt(keyspace string, end, start time.Time, iter *Iter) {
|
|
|
q.attempts++
|
|
q.attempts++
|
|
|
- q.totalLatency += d.Nanoseconds()
|
|
|
|
|
|
|
+ q.totalLatency += end.Sub(start).Nanoseconds()
|
|
|
// TODO: track latencies per host and things as well instead of just total
|
|
// TODO: track latencies per host and things as well instead of just total
|
|
|
|
|
+
|
|
|
|
|
+ if q.observer != nil {
|
|
|
|
|
+ q.observer.ObserveQuery(q.context, ObservedQuery{
|
|
|
|
|
+ Keyspace: keyspace,
|
|
|
|
|
+ Statement: q.stmt,
|
|
|
|
|
+ Start: start,
|
|
|
|
|
+ End: end,
|
|
|
|
|
+ Rows: iter.numRows,
|
|
|
|
|
+ Err: iter.err,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (q *Query) retryPolicy() RetryPolicy {
|
|
func (q *Query) retryPolicy() RetryPolicy {
|
|
@@ -1334,6 +1359,7 @@ type Batch struct {
|
|
|
Entries []BatchEntry
|
|
Entries []BatchEntry
|
|
|
Cons Consistency
|
|
Cons Consistency
|
|
|
rt RetryPolicy
|
|
rt RetryPolicy
|
|
|
|
|
+ observer BatchObserver
|
|
|
attempts int
|
|
attempts int
|
|
|
totalLatency int64
|
|
totalLatency int64
|
|
|
serialCons SerialConsistency
|
|
serialCons SerialConsistency
|
|
@@ -1357,6 +1383,7 @@ func (s *Session) NewBatch(typ BatchType) *Batch {
|
|
|
Type: typ,
|
|
Type: typ,
|
|
|
rt: s.cfg.RetryPolicy,
|
|
rt: s.cfg.RetryPolicy,
|
|
|
serialCons: s.cfg.SerialConsistency,
|
|
serialCons: s.cfg.SerialConsistency,
|
|
|
|
|
+ observer: s.batchObserver,
|
|
|
Cons: s.cons,
|
|
Cons: s.cons,
|
|
|
defaultTimestamp: s.cfg.DefaultTimestamp,
|
|
defaultTimestamp: s.cfg.DefaultTimestamp,
|
|
|
keyspace: s.cfg.Keyspace,
|
|
keyspace: s.cfg.Keyspace,
|
|
@@ -1365,6 +1392,13 @@ func (s *Session) NewBatch(typ BatchType) *Batch {
|
|
|
return batch
|
|
return batch
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Observer enables batch-level observer on this batch.
|
|
|
|
|
+// The provided observer will be called every time this batched query is executed.
|
|
|
|
|
+func (b *Batch) Observer(observer BatchObserver) *Batch {
|
|
|
|
|
+ b.observer = observer
|
|
|
|
|
+ return b
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func (b *Batch) Keyspace() string {
|
|
func (b *Batch) Keyspace() string {
|
|
|
return b.keyspace
|
|
return b.keyspace
|
|
|
}
|
|
}
|
|
@@ -1457,10 +1491,28 @@ func (b *Batch) WithTimestamp(timestamp int64) *Batch {
|
|
|
return b
|
|
return b
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (b *Batch) attempt(d time.Duration) {
|
|
|
|
|
|
|
+func (b *Batch) attempt(keyspace string, end, start time.Time, iter *Iter) {
|
|
|
b.attempts++
|
|
b.attempts++
|
|
|
- b.totalLatency += d.Nanoseconds()
|
|
|
|
|
|
|
+ b.totalLatency += end.Sub(start).Nanoseconds()
|
|
|
// TODO: track latencies per host and things as well instead of just total
|
|
// TODO: track latencies per host and things as well instead of just total
|
|
|
|
|
+
|
|
|
|
|
+ if b.observer == nil {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ statements := make([]string, len(b.Entries))
|
|
|
|
|
+ for i, entry := range b.Entries {
|
|
|
|
|
+ statements[i] = entry.Stmt
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ b.observer.ObserveBatch(b.context, ObservedBatch{
|
|
|
|
|
+ Keyspace: keyspace,
|
|
|
|
|
+ Statements: statements,
|
|
|
|
|
+ Start: start,
|
|
|
|
|
+ End: end,
|
|
|
|
|
+ // Rows not used in batch observations // TODO - might be able to support it when using BatchCAS
|
|
|
|
|
+ Err: iter.err,
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (b *Batch) GetRoutingKey() ([]byte, error) {
|
|
func (b *Batch) GetRoutingKey() ([]byte, error) {
|
|
@@ -1596,6 +1648,53 @@ func (t *traceWriter) Trace(traceId []byte) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+type ObservedQuery struct {
|
|
|
|
|
+ Keyspace string
|
|
|
|
|
+ Statement string
|
|
|
|
|
+
|
|
|
|
|
+ Start time.Time // time immediately before the query was called
|
|
|
|
|
+ End time.Time // time immediately after the query returned
|
|
|
|
|
+
|
|
|
|
|
+ // Rows is the number of rows in the current iter.
|
|
|
|
|
+ // In paginated queries, rows from previous scans are not counted.
|
|
|
|
|
+ // Rows is not used in batch queries and remains at the default value
|
|
|
|
|
+ Rows int
|
|
|
|
|
+
|
|
|
|
|
+ // 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
|
|
|
|
|
+ Err error
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// QueryObserver is the interface implemented by query observers / stat collectors.
|
|
|
|
|
+type QueryObserver interface {
|
|
|
|
|
+ // ObserveQuery gets called on every query to cassandra, including all queries in an iterator when paging is enabled.
|
|
|
|
|
+ // It doesn't get called if there is no query because the session is closed or there are no connections available.
|
|
|
|
|
+ // The error reported only shows query errors, i.e. if a SELECT is valid but finds no matches it will be nil.
|
|
|
|
|
+ ObserveQuery(context.Context, ObservedQuery)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type ObservedBatch struct {
|
|
|
|
|
+ Keyspace string
|
|
|
|
|
+ Statements []string
|
|
|
|
|
+
|
|
|
|
|
+ Start time.Time // time immediately before the batch query was called
|
|
|
|
|
+ End time.Time // time immediately after the batch query returned
|
|
|
|
|
+
|
|
|
|
|
+ // Err is the error in the batch query.
|
|
|
|
|
+ // It only tracks network errors or errors of bad cassandra syntax, in particular selects with no match return nil error
|
|
|
|
|
+ Err error
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// BatchObserver is the interface implemented by batch observers / stat collectors.
|
|
|
|
|
+type BatchObserver interface {
|
|
|
|
|
+ // ObserveBatch gets called on every batch query to cassandra.
|
|
|
|
|
+ // It also gets called once for each query in a batch.
|
|
|
|
|
+ // It doesn't get called if there is no query because the session is closed or there are no connections available.
|
|
|
|
|
+ // The error reported only shows query errors, i.e. if a SELECT is valid but finds no matches it will be nil.
|
|
|
|
|
+ // Unlike QueryObserver.ObserveQuery it does no reporting on rows read.
|
|
|
|
|
+ ObserveBatch(context.Context, ObservedBatch)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
type Error struct {
|
|
type Error struct {
|
|
|
Code int
|
|
Code int
|
|
|
Message string
|
|
Message string
|