Add diagnostic events base classes

patch by Stefan Podkowinski; reviewed by Mick Semb Wever for CASSANDRA-13457


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/2846b22a
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/2846b22a
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/2846b22a

Branch: refs/heads/trunk
Commit: 2846b22a70d48bae25203be945e02dd3b6cfda56
Parents: d3e6891
Author: Stefan Podkowinski <stefan.podkowin...@1und1.de>
Authored: Thu Mar 16 12:50:52 2017 +0100
Committer: Stefan Podkowinski <stefan.podkowin...@1und1.de>
Committed: Fri Aug 17 14:06:57 2018 +0200

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 conf/cassandra.yaml                             |   5 +
 .../org/apache/cassandra/config/Config.java     |   3 +
 .../cassandra/config/DatabaseDescriptor.java    |  11 +
 .../schema/AlterKeyspaceStatement.java          |   5 +
 .../statements/schema/AlterTableStatement.java  |   5 +
 .../statements/schema/AlterTypeStatement.java   |   5 +
 .../statements/schema/AlterViewStatement.java   |   5 +
 .../schema/CreateAggregateStatement.java        |   5 +
 .../schema/CreateFunctionStatement.java         |   5 +
 .../statements/schema/CreateIndexStatement.java |   5 +
 .../schema/CreateKeyspaceStatement.java         |   5 +
 .../statements/schema/CreateTableStatement.java |   5 +
 .../schema/CreateTriggerStatement.java          |   5 +
 .../statements/schema/CreateTypeStatement.java  |   5 +
 .../statements/schema/CreateViewStatement.java  |   5 +
 .../schema/DropAggregateStatement.java          |   5 +
 .../schema/DropFunctionStatement.java           |   5 +
 .../statements/schema/DropIndexStatement.java   |   5 +
 .../schema/DropKeyspaceStatement.java           |   5 +
 .../statements/schema/DropTableStatement.java   |   5 +
 .../statements/schema/DropTriggerStatement.java |   5 +
 .../statements/schema/DropTypeStatement.java    |   5 +
 .../statements/schema/DropViewStatement.java    |   5 +
 .../org/apache/cassandra/dht/BootStrapper.java  |  14 +-
 .../cassandra/dht/BootstrapDiagnostics.java     |  80 +++++
 .../apache/cassandra/dht/BootstrapEvent.java    |  82 +++++
 .../NoReplicationTokenAllocator.java            |   4 +
 .../ReplicationAwareTokenAllocator.java         |   7 +-
 .../TokenAllocatorDiagnostics.java              | 195 ++++++++++++
 .../dht/tokenallocator/TokenAllocatorEvent.java | 113 +++++++
 .../tokenallocator/TokenAllocatorFactory.java   |   8 +-
 .../apache/cassandra/diag/DiagnosticEvent.java  |  50 +++
 .../cassandra/diag/DiagnosticEventService.java  | 291 +++++++++++++++++
 src/java/org/apache/cassandra/gms/Gossiper.java |  46 ++-
 .../cassandra/gms/GossiperDiagnostics.java      | 113 +++++++
 .../org/apache/cassandra/gms/GossiperEvent.java | 111 +++++++
 src/java/org/apache/cassandra/hints/Hint.java   |   2 +-
 .../apache/cassandra/hints/HintDiagnostics.java |  85 +++++
 .../org/apache/cassandra/hints/HintEvent.java   | 102 ++++++
 .../cassandra/hints/HintsDispatchExecutor.java  |  10 +
 .../apache/cassandra/hints/HintsDispatcher.java |  52 +--
 .../apache/cassandra/hints/HintsService.java    |  12 +-
 .../hints/HintsServiceDiagnostics.java          |  65 ++++
 .../cassandra/hints/HintsServiceEvent.java      |  71 +++++
 .../apache/cassandra/locator/TokenMetadata.java |   2 +
 .../locator/TokenMetadataDiagnostics.java       |  46 +++
 .../cassandra/locator/TokenMetadataEvent.java   |  62 ++++
 src/java/org/apache/cassandra/schema/Diff.java  |   5 +
 .../cassandra/schema/MigrationManager.java      |  33 +-
 .../apache/cassandra/schema/MigrationTask.java  |   5 +
 .../org/apache/cassandra/schema/Schema.java     |  34 ++
 .../schema/SchemaAnnouncementDiagnostics.java   |  60 ++++
 .../schema/SchemaAnnouncementEvent.java         | 104 ++++++
 .../cassandra/schema/SchemaDiagnostics.java     | 178 +++++++++++
 .../apache/cassandra/schema/SchemaEvent.java    | 318 +++++++++++++++++++
 .../schema/SchemaMigrationDiagnostics.java      |  83 +++++
 .../cassandra/schema/SchemaMigrationEvent.java  | 114 +++++++
 .../cassandra/schema/SchemaPushVerbHandler.java |   1 +
 .../service/PendingRangeCalculatorService.java  |  23 +-
 ...endingRangeCalculatorServiceDiagnostics.java |  73 +++++
 .../PendingRangeCalculatorServiceEvent.java     |  69 ++++
 .../diag/DiagnosticEventServiceTest.java        | 244 ++++++++++++++
 63 files changed, 3047 insertions(+), 40 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index d8aca56..ceba843 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 4.0
+ * Add base classes for diagnostic events (CASSANDRA-13457)
  * Clear view system metadata when dropping keyspace (CASSANDRA-14646)
  * Allocate ReentrantLock on-demand in java11 AtomicBTreePartitionerBase 
(CASSANDRA-14637)
  * Make all existing virtual tables use LocalPartitioner (CASSANDRA-14640)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/conf/cassandra.yaml
----------------------------------------------------------------------
diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml
index 663daaa..c7c2785 100644
--- a/conf/cassandra.yaml
+++ b/conf/cassandra.yaml
@@ -1217,3 +1217,8 @@ audit_logging_options:
 # validate tombstones on reads and compaction
 # can be either "disabled", "warn" or "exception"
 # corrupted_tombstone_strategy: disabled
+
+# Diagnostic Events #
+# If enabled, diagnostic events can be helpful for troubleshooting operational 
issues. Emitted events contain details
+# on internal state and temporal relationships across events, accessible by 
clients via JMX.
+diagnostic_events_enabled: false

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/config/Config.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/Config.java 
b/src/java/org/apache/cassandra/config/Config.java
index 3a7ff0d..46dbc1c 100644
--- a/src/java/org/apache/cassandra/config/Config.java
+++ b/src/java/org/apache/cassandra/config/Config.java
@@ -386,6 +386,9 @@ public class Config
     public volatile AuditLogOptions audit_logging_options = new 
AuditLogOptions();
 
     public CorruptedTombstoneStrategy corrupted_tombstone_strategy = 
CorruptedTombstoneStrategy.disabled;
+
+    public volatile boolean diagnostic_events_enabled = false;
+
     /**
      * @deprecated migrate to {@link DatabaseDescriptor#isClientInitialized()}
      */

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java 
b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index 366dac7..65a34f0 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -2536,6 +2536,17 @@ public class DatabaseDescriptor
         return conf.back_pressure_enabled;
     }
 
+    public static boolean diagnosticEventsEnabled()
+    {
+        return conf.diagnostic_events_enabled;
+    }
+
+    @VisibleForTesting
+    public static void setDiagnosticEventsEnabled(boolean enabled)
+    {
+        conf.diagnostic_events_enabled = enabled;
+    }
+
     @VisibleForTesting
     public static void setBackPressureStrategy(BackPressureStrategy strategy)
     {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
index c2d0e4c..12e73d0 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
@@ -95,6 +95,11 @@ public final class AlterKeyspaceStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.ALTER_KEYSPACE, 
keyspaceName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s)", getClass().getSimpleName(), 
keyspaceName);
+    }
+
     public static final class Raw extends CQLStatement.Raw
     {
         private final String keyspaceName;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
index a081a2c..3ec75b2 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
@@ -81,6 +81,11 @@ public abstract class AlterTableStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.ALTER_TABLE, 
keyspaceName, tableName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, tableName);
+    }
+
     abstract KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata 
table);
 
     /**

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
index 50f09a0..6eab2ba 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
@@ -83,6 +83,11 @@ public abstract class AlterTypeStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.ALTER_TYPE, keyspaceName, 
typeName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, typeName);
+    }
+
     private static final class AddField extends AlterTypeStatement
     {
         private final FieldIdentifier fieldName;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java
index 2ecc095..1931bb4 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java
@@ -92,6 +92,11 @@ public final class AlterViewStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.ALTER_VIEW, keyspaceName, 
viewName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, viewName);
+    }
+
     public static final class Raw extends CQLStatement.Raw
     {
         private final QualifiedName name;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
index cd9808a..462623d 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
@@ -271,6 +271,11 @@ public final class CreateAggregateStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.CREATE_AGGREGATE, 
keyspaceName, aggregateName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, aggregateName);
+    }
+
     private String stateFunctionString()
     {
         return format("%s(%s)", stateFunctionName, join(", ", 
transform(concat(singleton(rawStateType), rawArgumentTypes), 
Object::toString)));

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
index 13e173f..20c4ad9 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
@@ -198,6 +198,11 @@ public final class CreateFunctionStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.CREATE_FUNCTION, 
keyspaceName, functionName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, functionName);
+    }
+
     public static final class Raw extends CQLStatement.Raw
     {
         private final FunctionName name;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
index 0065a4c..df41358 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
@@ -194,6 +194,11 @@ public final class CreateIndexStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.CREATE_INDEX, 
keyspaceName, indexName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, indexName);
+    }
+
     public static final class Raw extends CQLStatement.Raw
     {
         private final QualifiedName tableName;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
index ecd19ed..f85a4e9 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
@@ -94,6 +94,11 @@ public final class CreateKeyspaceStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.CREATE_KEYSPACE, 
keyspaceName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s)", getClass().getSimpleName(), 
keyspaceName);
+    }
+
     public static final class Raw extends CQLStatement.Raw
     {
         public final String keyspaceName;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
index ff26f0d..62fcafe 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
@@ -123,6 +123,11 @@ public final class CreateTableStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.CREATE_TABLE, 
keyspaceName, tableName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, tableName);
+    }
+
     public TableMetadata.Builder builder(Types types)
     {
         attrs.validate();

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java
index cb6d14e..e85ffd8 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java
@@ -96,6 +96,11 @@ public final class CreateTriggerStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.CREATE_TRIGGER, 
keyspaceName, triggerName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, triggerName);
+    }
+
     public static final class Raw extends CQLStatement.Raw
     {
         private final QualifiedName tableName;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java
index c328eb7..7c1717e 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java
@@ -115,6 +115,11 @@ public final class CreateTypeStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.CREATE_TYPE, 
keyspaceName, typeName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, typeName);
+    }
+
     public static final class Raw extends CQLStatement.Raw
     {
         private final UTName name;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
index f97b0fe..5f62001 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
@@ -341,6 +341,11 @@ public final class CreateViewStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.CREATE_VIEW, 
keyspaceName, viewName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, viewName);
+    }
+
     public final static class Raw extends CQLStatement.Raw
     {
         private final QualifiedName tableName;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java
index 564f267..d24f77e 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java
@@ -139,6 +139,11 @@ public final class DropAggregateStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.DROP_AGGREGATE, 
keyspaceName, aggregateName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, aggregateName);
+    }
+
     private List<AbstractType<?>> prepareArgumentTypes(Types types)
     {
         return arguments.stream()

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java
index 9433833..f7d7d4a 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java
@@ -147,6 +147,11 @@ public final class DropFunctionStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.DROP_FUNCTION, 
keyspaceName, functionName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, functionName);
+    }
+
     private List<AbstractType<?>> prepareArgumentTypes(Types types)
     {
         return arguments.stream()

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java
index 6a6f8d9..2186470 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java
@@ -90,6 +90,11 @@ public final class DropIndexStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.DROP_INDEX, keyspaceName, 
indexName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, indexName);
+    }
+
     public static final class Raw extends CQLStatement.Raw
     {
         private final QualifiedName name;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java
index ae5cf06..f2bd30b 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java
@@ -64,6 +64,11 @@ public final class DropKeyspaceStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.DROP_KEYSPACE, 
keyspaceName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s)", getClass().getSimpleName(), 
keyspaceName);
+    }
+
     public static final class Raw extends CQLStatement.Raw
     {
         private final String keyspaceName;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java
index 9be59af..15c2a03 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java
@@ -92,6 +92,11 @@ public final class DropTableStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.DROP_TABLE, keyspaceName, 
tableName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, tableName);
+    }
+
     public static final class Raw extends CQLStatement.Raw
     {
         private final QualifiedName name;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java
index 8de47c2..967e568 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java
@@ -82,6 +82,11 @@ public final class DropTriggerStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.DROP_TRIGGER, 
keyspaceName, triggerName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, triggerName);
+    }
+
     public static final class Raw extends CQLStatement.Raw
     {
         private final QualifiedName tableName;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java
index d51954c..6cda7ba 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java
@@ -129,6 +129,11 @@ public final class DropTypeStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.DROP_TYPE, keyspaceName, 
typeName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, typeName);
+    }
+
     public static final class Raw extends CQLStatement.Raw
     {
         private final UTName name;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java
index 807d03d..2c73717 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java
@@ -78,6 +78,11 @@ public final class DropViewStatement extends 
AlterSchemaStatement
         return new AuditLogContext(AuditLogEntryType.DROP_VIEW, keyspaceName, 
viewName);
     }
 
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), 
keyspaceName, viewName);
+    }
+
     public static final class Raw extends CQLStatement.Raw
     {
         private final QualifiedName name;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/dht/BootStrapper.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/dht/BootStrapper.java 
b/src/java/org/apache/cassandra/dht/BootStrapper.java
index 432586b..92bf8c8 100644
--- a/src/java/org/apache/cassandra/dht/BootStrapper.java
+++ b/src/java/org/apache/cassandra/dht/BootStrapper.java
@@ -168,7 +168,11 @@ public class BootStrapper extends 
ProgressEventNotifierSupport
 
         // if user specified tokens, use those
         if (initialTokens.size() > 0)
-            return getSpecifiedTokens(metadata, initialTokens);
+        {
+            Collection<Token> tokens = getSpecifiedTokens(metadata, 
initialTokens);
+            BootstrapDiagnostics.useSpecifiedTokens(address, 
allocationKeyspace, tokens, DatabaseDescriptor.getNumTokens());
+            return tokens;
+        }
 
         int numTokens = DatabaseDescriptor.getNumTokens();
         if (numTokens < 1)
@@ -180,7 +184,9 @@ public class BootStrapper extends 
ProgressEventNotifierSupport
         if (numTokens == 1)
             logger.warn("Picking random token for a single vnode.  You should 
probably add more vnodes and/or use the automatic token allocation mechanism.");
 
-        return getRandomTokens(metadata, numTokens);
+        Collection<Token> tokens = getRandomTokens(metadata, numTokens);
+        BootstrapDiagnostics.useRandomTokens(address, metadata, numTokens, 
tokens);
+        return tokens;
     }
 
     private static Collection<Token> getSpecifiedTokens(final TokenMetadata 
metadata,
@@ -213,7 +219,9 @@ public class BootStrapper extends 
ProgressEventNotifierSupport
             throw new ConfigurationException("Problem opening token allocation 
keyspace " + allocationKeyspace);
         AbstractReplicationStrategy rs = ks.getReplicationStrategy();
 
-        return TokenAllocation.allocateTokens(metadata, rs, address, 
numTokens);
+        Collection<Token> tokens = TokenAllocation.allocateTokens(metadata, 
rs, address, numTokens);
+        BootstrapDiagnostics.tokensAllocated(address, metadata, 
allocationKeyspace, numTokens, tokens);
+        return tokens;
     }
 
     public static Collection<Token> getRandomTokens(TokenMetadata metadata, 
int numTokens)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/dht/BootstrapDiagnostics.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/dht/BootstrapDiagnostics.java 
b/src/java/org/apache/cassandra/dht/BootstrapDiagnostics.java
new file mode 100644
index 0000000..5695532
--- /dev/null
+++ b/src/java/org/apache/cassandra/dht/BootstrapDiagnostics.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.dht;
+
+import java.util.Collection;
+import com.google.common.collect.ImmutableList;
+
+import org.apache.cassandra.dht.BootstrapEvent.BootstrapEventType;
+import org.apache.cassandra.diag.DiagnosticEventService;
+import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.locator.TokenMetadata;
+
+/**
+ * Utility methods for bootstrap related activities.
+ */
+final class BootstrapDiagnostics
+{
+    private static final DiagnosticEventService service = 
DiagnosticEventService.instance();
+
+    private BootstrapDiagnostics()
+    {
+    }
+
+    static void useSpecifiedTokens(InetAddressAndPort address, String 
allocationKeyspace, Collection<Token> initialTokens,
+                                   int numTokens)
+    {
+        if (isEnabled(BootstrapEventType.BOOTSTRAP_USING_SPECIFIED_TOKENS))
+            service.publish(new 
BootstrapEvent(BootstrapEventType.BOOTSTRAP_USING_SPECIFIED_TOKENS,
+                                               address,
+                                               null,
+                                               allocationKeyspace,
+                                               numTokens,
+                                               
ImmutableList.copyOf(initialTokens)));
+    }
+
+    static void useRandomTokens(InetAddressAndPort address, TokenMetadata 
metadata, int numTokens, Collection<Token> tokens)
+    {
+        if (isEnabled(BootstrapEventType.BOOTSTRAP_USING_RANDOM_TOKENS))
+            service.publish(new 
BootstrapEvent(BootstrapEventType.BOOTSTRAP_USING_RANDOM_TOKENS,
+                                               address,
+                                               metadata.cloneOnlyTokenMap(),
+                                               null,
+                                               numTokens,
+                                               ImmutableList.copyOf(tokens)));
+    }
+
+    static void tokensAllocated(InetAddressAndPort address, TokenMetadata 
metadata,
+                                String allocationKeyspace, int numTokens, 
Collection<Token> tokens)
+    {
+        if (isEnabled(BootstrapEventType.TOKENS_ALLOCATED))
+            service.publish(new 
BootstrapEvent(BootstrapEventType.TOKENS_ALLOCATED,
+                                               address,
+                                               metadata.cloneOnlyTokenMap(),
+                                               allocationKeyspace,
+                                               numTokens,
+                                               ImmutableList.copyOf(tokens)));
+    }
+
+    private static boolean isEnabled(BootstrapEventType type)
+    {
+        return service.isEnabled(BootstrapEvent.class, type);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/dht/BootstrapEvent.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/dht/BootstrapEvent.java 
b/src/java/org/apache/cassandra/dht/BootstrapEvent.java
new file mode 100644
index 0000000..5bad09a
--- /dev/null
+++ b/src/java/org/apache/cassandra/dht/BootstrapEvent.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.dht;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableCollection;
+
+import org.apache.cassandra.diag.DiagnosticEvent;
+import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.locator.TokenMetadata;
+
+/**
+ * DiagnosticEvent implementation for bootstrap related activities.
+ */
+final class BootstrapEvent extends DiagnosticEvent
+{
+
+    private final BootstrapEventType type;
+    @Nullable
+    private final TokenMetadata tokenMetadata;
+    private final InetAddressAndPort address;
+    @Nullable
+    private final String allocationKeyspace;
+    private final Integer numTokens;
+    private final Collection<Token> tokens;
+
+    BootstrapEvent(BootstrapEventType type, InetAddressAndPort address, 
@Nullable TokenMetadata tokenMetadata,
+                   @Nullable String allocationKeyspace, int numTokens, 
ImmutableCollection<Token> tokens)
+    {
+        this.type = type;
+        this.address = address;
+        this.tokenMetadata = tokenMetadata;
+        this.allocationKeyspace = allocationKeyspace;
+        this.numTokens = numTokens;
+        this.tokens = tokens;
+    }
+
+    enum BootstrapEventType
+    {
+        BOOTSTRAP_USING_SPECIFIED_TOKENS,
+        BOOTSTRAP_USING_RANDOM_TOKENS,
+        TOKENS_ALLOCATED
+    }
+
+
+    public BootstrapEventType getType()
+    {
+        return type;
+    }
+
+    public Map<String, Serializable> toMap()
+    {
+        // be extra defensive against nulls and bugs
+        HashMap<String, Serializable> ret = new HashMap<>();
+        ret.put("tokenMetadata", String.valueOf(tokenMetadata));
+        ret.put("allocationKeyspace", allocationKeyspace);
+        ret.put("numTokens", numTokens);
+        ret.put("tokens", String.valueOf(tokens));
+        return ret;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/dht/tokenallocator/NoReplicationTokenAllocator.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/dht/tokenallocator/NoReplicationTokenAllocator.java
 
b/src/java/org/apache/cassandra/dht/tokenallocator/NoReplicationTokenAllocator.java
index 54d80dc..f6a1592 100644
--- 
a/src/java/org/apache/cassandra/dht/tokenallocator/NoReplicationTokenAllocator.java
+++ 
b/src/java/org/apache/cassandra/dht/tokenallocator/NoReplicationTokenAllocator.java
@@ -86,6 +86,7 @@ public class NoReplicationTokenAllocator<Unit> extends 
TokenAllocatorBase<Unit>
             sortedUnits.add(new Weighted<UnitInfo>(unitInfo.ownership, 
unitInfo));
         }
 
+        TokenAllocatorDiagnostics.tokenInfosCreated(this, sortedUnits, 
sortedTokens, first);
         return first;
     }
 
@@ -127,6 +128,7 @@ public class NoReplicationTokenAllocator<Unit> extends 
TokenAllocatorBase<Unit>
         }
         unitInfos.put(newUnit.unit, newUnit);
         createTokenInfos(unitInfos);
+        TokenAllocatorDiagnostics.randomTokensGenerated(this, numTokens, 
sortedUnits, sortedTokens, newUnit.unit, tokens);
         return tokens;
     }
 
@@ -232,6 +234,7 @@ public class NoReplicationTokenAllocator<Unit> extends 
TokenAllocatorBase<Unit>
         }
         sortedUnits.add(new Weighted<>(newUnitInfo.ownership, newUnitInfo));
 
+        TokenAllocatorDiagnostics.unitedAdded(this, numTokens, sortedUnits, 
sortedTokens, newTokens, newUnit);
         return newTokens;
     }
 
@@ -257,6 +260,7 @@ public class NoReplicationTokenAllocator<Unit> extends 
TokenAllocatorBase<Unit>
             tokens.add(tokenInfo.value.token);
         }
         sortedTokens.keySet().removeAll(tokens);
+        TokenAllocatorDiagnostics.unitRemoved(this, n, sortedUnits, 
sortedTokens);
     }
 
     public int getReplicas()

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/dht/tokenallocator/ReplicationAwareTokenAllocator.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/dht/tokenallocator/ReplicationAwareTokenAllocator.java
 
b/src/java/org/apache/cassandra/dht/tokenallocator/ReplicationAwareTokenAllocator.java
index 87dba59..efd2766 100644
--- 
a/src/java/org/apache/cassandra/dht/tokenallocator/ReplicationAwareTokenAllocator.java
+++ 
b/src/java/org/apache/cassandra/dht/tokenallocator/ReplicationAwareTokenAllocator.java
@@ -132,7 +132,9 @@ class ReplicationAwareTokenAllocator<Unit> extends 
TokenAllocatorBase<Unit>
             }
         }
 
-        return ImmutableList.copyOf(unitToTokens.get(newUnit));
+        ImmutableList<Token> newTokens = 
ImmutableList.copyOf(unitToTokens.get(newUnit));
+        TokenAllocatorDiagnostics.unitedAdded(this, numTokens, unitToTokens, 
sortedTokens, newTokens, newUnit);
+        return newTokens;
     }
 
     private Collection<Token> generateRandomTokens(Unit newUnit, int numTokens)
@@ -148,6 +150,7 @@ class ReplicationAwareTokenAllocator<Unit> extends 
TokenAllocatorBase<Unit>
                 unitToTokens.put(newUnit, token);
             }
         }
+        TokenAllocatorDiagnostics.randomTokensGenerated(this, numTokens, 
unitToTokens, sortedTokens, newUnit, tokens);
         return tokens;
     }
 
@@ -176,6 +179,7 @@ class ReplicationAwareTokenAllocator<Unit> extends 
TokenAllocatorBase<Unit>
             curr = curr.next;
         } while (curr != first);
 
+        TokenAllocatorDiagnostics.tokenInfosCreated(this, unitToTokens, first);
         return first;
     }
 
@@ -526,6 +530,7 @@ class ReplicationAwareTokenAllocator<Unit> extends 
TokenAllocatorBase<Unit>
     {
         Collection<Token> tokens = unitToTokens.removeAll(n);
         sortedTokens.keySet().removeAll(tokens);
+        TokenAllocatorDiagnostics.unitRemoved(this, n, unitToTokens, 
sortedTokens);
     }
 
     public int unitCount()

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/dht/tokenallocator/TokenAllocatorDiagnostics.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/dht/tokenallocator/TokenAllocatorDiagnostics.java
 
b/src/java/org/apache/cassandra/dht/tokenallocator/TokenAllocatorDiagnostics.java
new file mode 100644
index 0000000..cc27a47
--- /dev/null
+++ 
b/src/java/org/apache/cassandra/dht/tokenallocator/TokenAllocatorDiagnostics.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.dht.tokenallocator;
+
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Queue;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+
+import org.apache.cassandra.dht.Token;
+import org.apache.cassandra.dht.tokenallocator.TokenAllocatorBase.TokenInfo;
+import org.apache.cassandra.dht.tokenallocator.TokenAllocatorBase.UnitInfo;
+import org.apache.cassandra.dht.tokenallocator.TokenAllocatorBase.Weighted;
+import 
org.apache.cassandra.dht.tokenallocator.TokenAllocatorEvent.TokenAllocatorEventType;
+import org.apache.cassandra.diag.DiagnosticEventService;
+
+/**
+ * Utility methods for DiagnosticEvent around {@link TokenAllocator} 
activities.
+ */
+final class TokenAllocatorDiagnostics
+{
+    private static final DiagnosticEventService service = 
DiagnosticEventService.instance();
+
+    private TokenAllocatorDiagnostics()
+    {
+    }
+
+    static <Unit> void 
noReplicationTokenAllocatorInstanciated(NoReplicationTokenAllocator<Unit> 
allocator)
+    {
+        if 
(isEnabled(TokenAllocatorEventType.NO_REPLICATION_AWARE_TOKEN_ALLOCATOR_INSTANCIATED))
+            service.publish(new 
TokenAllocatorEvent<>(TokenAllocatorEventType.NO_REPLICATION_AWARE_TOKEN_ALLOCATOR_INSTANCIATED,
+                                                      allocator, null, null, 
null, null, null, null, null));
+    }
+
+    static <Unit> void 
replicationTokenAllocatorInstanciated(ReplicationAwareTokenAllocator<Unit> 
allocator)
+    {
+        if 
(isEnabled(TokenAllocatorEventType.REPLICATION_AWARE_TOKEN_ALLOCATOR_INSTANCIATED))
+            service.publish(new 
TokenAllocatorEvent<>(TokenAllocatorEventType.REPLICATION_AWARE_TOKEN_ALLOCATOR_INSTANCIATED,
+                                                      allocator, null, null, 
null,null, null, null, null));
+    }
+
+    static <Unit> void unitedAdded(TokenAllocatorBase<Unit> allocator, int 
numTokens,
+                                   Queue<Weighted<UnitInfo>> sortedUnits, 
NavigableMap<Token, Unit> sortedTokens,
+                                   List<Token> tokens, Unit unit)
+    {
+        if (isEnabled(TokenAllocatorEventType.UNIT_ADDED))
+            service.publish(new 
TokenAllocatorEvent<>(TokenAllocatorEventType.UNIT_ADDED,
+                                                      allocator,
+                                                      numTokens,
+                                                      
ImmutableList.copyOf(sortedUnits),
+                                                      null,
+                                                      
ImmutableMap.copyOf(sortedTokens),
+                                                      
ImmutableList.copyOf(tokens),
+                                                      unit,
+                                                      null));
+    }
+
+    static <Unit> void unitedAdded(TokenAllocatorBase<Unit> allocator, int 
numTokens,
+                                   Multimap<Unit, Token> unitToTokens, 
NavigableMap<Token, Unit> sortedTokens,
+                                   List<Token> tokens, Unit unit)
+    {
+        if (isEnabled(TokenAllocatorEventType.UNIT_ADDED))
+            service.publish(new 
TokenAllocatorEvent<>(TokenAllocatorEventType.UNIT_ADDED,
+                                                      allocator,
+                                                      numTokens,
+                                                      null,
+                                                      
ImmutableMap.copyOf(unitToTokens.asMap()),
+                                                      
ImmutableMap.copyOf(sortedTokens),
+                                                      
ImmutableList.copyOf(tokens),
+                                                      unit,
+                                                      null));
+    }
+
+
+    static <Unit> void unitRemoved(TokenAllocatorBase<Unit> allocator, Unit 
unit,
+                                   Queue<Weighted<UnitInfo>> sortedUnits, 
Map<Token, Unit> sortedTokens)
+    {
+        if (isEnabled(TokenAllocatorEventType.UNIT_REMOVED))
+            service.publish(new 
TokenAllocatorEvent<>(TokenAllocatorEventType.UNIT_REMOVED,
+                                                      allocator,
+                                                      null,
+                                                      
ImmutableList.copyOf(sortedUnits),
+                                                      null,
+                                                      
ImmutableMap.copyOf(sortedTokens),
+                                                      null,
+                                                      unit,
+                                                      null));
+    }
+
+    static <Unit> void unitRemoved(TokenAllocatorBase<Unit> allocator, Unit 
unit,
+                                   Multimap<Unit, Token> unitToTokens, 
Map<Token, Unit> sortedTokens)
+    {
+        if (isEnabled(TokenAllocatorEventType.UNIT_REMOVED))
+            service.publish(new 
TokenAllocatorEvent<>(TokenAllocatorEventType.UNIT_REMOVED,
+                                                      allocator,
+                                                      null,
+                                                      null,
+                                                      
ImmutableMap.copyOf(unitToTokens.asMap()),
+                                                      
ImmutableMap.copyOf(sortedTokens),
+                                                      null,
+                                                      unit,
+                                                      null));
+    }
+
+    static <Unit> void tokenInfosCreated(TokenAllocatorBase<Unit> allocator, 
Queue<Weighted<UnitInfo>> sortedUnits,
+                                         Map<Token, Unit> sortedTokens, 
TokenInfo<Unit> tokenInfo)
+    {
+        if (isEnabled(TokenAllocatorEventType.TOKEN_INFOS_CREATED))
+            service.publish(new 
TokenAllocatorEvent<>(TokenAllocatorEventType.TOKEN_INFOS_CREATED,
+                                                      allocator,
+                                                      null,
+                                                      
ImmutableList.copyOf(sortedUnits),
+                                                      null,
+                                                      
ImmutableMap.copyOf(sortedTokens),
+                                                      null,
+                                                      null,
+                                                      tokenInfo));
+    }
+
+    static <Unit> void tokenInfosCreated(TokenAllocatorBase<Unit> allocator, 
Multimap<Unit, Token> unitToTokens,
+                                         TokenInfo<Unit> tokenInfo)
+    {
+        if (isEnabled(TokenAllocatorEventType.TOKEN_INFOS_CREATED))
+            service.publish(new 
TokenAllocatorEvent<>(TokenAllocatorEventType.TOKEN_INFOS_CREATED,
+                                                      allocator,
+                                                      null,
+                                                      null,
+                                                      
ImmutableMap.copyOf(unitToTokens.asMap()),
+                                                      null,
+                                                      null,
+                                                      null,
+                                                      tokenInfo));
+    }
+
+    static <Unit> void randomTokensGenerated(TokenAllocatorBase<Unit> 
allocator,
+                                             int numTokens, 
Queue<Weighted<UnitInfo>> sortedUnits,
+                                             NavigableMap<Token, Unit> 
sortedTokens, Unit newUnit,
+                                             Set<Token> tokens)
+    {
+        if (isEnabled(TokenAllocatorEventType.RANDOM_TOKENS_GENERATED))
+            service.publish(new 
TokenAllocatorEvent<>(TokenAllocatorEventType.RANDOM_TOKENS_GENERATED,
+                                                      allocator,
+                                                      numTokens,
+                                                      
ImmutableList.copyOf(sortedUnits),
+                                                      null,
+                                                      
ImmutableMap.copyOf(sortedTokens),
+                                                      
ImmutableList.copyOf(tokens),
+                                                      newUnit,
+                                                      null));
+    }
+
+    static <Unit> void randomTokensGenerated(TokenAllocatorBase<Unit> 
allocator,
+                                             int numTokens, Multimap<Unit, 
Token> unitToTokens,
+                                             NavigableMap<Token, Unit> 
sortedTokens, Unit newUnit,
+                                             Set<Token> tokens)
+    {
+        if (isEnabled(TokenAllocatorEventType.RANDOM_TOKENS_GENERATED))
+            service.publish(new 
TokenAllocatorEvent<>(TokenAllocatorEventType.RANDOM_TOKENS_GENERATED,
+                                                      allocator,
+                                                      numTokens,
+                                                      null,
+                                                      
ImmutableMap.copyOf(unitToTokens.asMap()),
+                                                      
ImmutableMap.copyOf(sortedTokens),
+                                                      
ImmutableList.copyOf(tokens),
+                                                      newUnit,
+                                                      null));
+    }
+
+    private static boolean isEnabled(TokenAllocatorEventType type)
+    {
+        return service.isEnabled(TokenAllocatorEvent.class, type);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/dht/tokenallocator/TokenAllocatorEvent.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/dht/tokenallocator/TokenAllocatorEvent.java 
b/src/java/org/apache/cassandra/dht/tokenallocator/TokenAllocatorEvent.java
new file mode 100644
index 0000000..ca59938
--- /dev/null
+++ b/src/java/org/apache/cassandra/dht/tokenallocator/TokenAllocatorEvent.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.dht.tokenallocator;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.apache.cassandra.dht.Token;
+import org.apache.cassandra.dht.tokenallocator.TokenAllocatorBase.TokenInfo;
+import org.apache.cassandra.dht.tokenallocator.TokenAllocatorBase.UnitInfo;
+import org.apache.cassandra.dht.tokenallocator.TokenAllocatorBase.Weighted;
+import org.apache.cassandra.diag.DiagnosticEvent;
+
+/**
+ * DiagnosticEvent implementation for {@link TokenAllocator} activities.
+ */
+final class TokenAllocatorEvent<Unit> extends DiagnosticEvent
+{
+
+    private final TokenAllocatorEventType type;
+    private final TokenAllocatorBase<Unit> allocator;
+    private final int replicas;
+    @Nullable
+    private final Integer numTokens;
+    @Nullable
+    private final Collection<Weighted<UnitInfo>> sortedUnits;
+    @Nullable
+    private final Map<Unit, Collection<Token>> unitToTokens;
+    @Nullable
+    private final ImmutableMap<Token, Unit> sortedTokens;
+    @Nullable
+    private final List<Token> tokens;
+    @Nullable
+    private final Unit unit;
+    @Nullable
+    private final TokenInfo<Unit> tokenInfo;
+
+    TokenAllocatorEvent(TokenAllocatorEventType type, TokenAllocatorBase<Unit> 
allocator, @Nullable Integer numTokens,
+                        @Nullable ImmutableList<Weighted<UnitInfo>> 
sortedUnits, @Nullable ImmutableMap<Unit, Collection<Token>> unitToTokens,
+                        @Nullable ImmutableMap<Token, Unit> sortedTokens, 
@Nullable ImmutableList<Token> tokens, Unit unit,
+                        @Nullable TokenInfo<Unit> tokenInfo)
+    {
+        this.type = type;
+        this.allocator = allocator;
+        this.replicas = allocator.getReplicas();
+        this.numTokens = numTokens;
+        this.sortedUnits = sortedUnits;
+        this.unitToTokens = unitToTokens;
+        this.sortedTokens = sortedTokens;
+        this.tokens = tokens;
+        this.unit = unit;
+        this.tokenInfo = tokenInfo;
+    }
+
+    enum TokenAllocatorEventType
+    {
+        REPLICATION_AWARE_TOKEN_ALLOCATOR_INSTANCIATED,
+        NO_REPLICATION_AWARE_TOKEN_ALLOCATOR_INSTANCIATED,
+        UNIT_ADDED,
+        UNIT_REMOVED,
+        TOKEN_INFOS_CREATED,
+        RANDOM_TOKENS_GENERATED,
+        TOKENS_ALLOCATED
+    }
+
+    public TokenAllocatorEventType getType()
+    {
+        return type;
+    }
+
+    public HashMap<String, Serializable> toMap()
+    {
+        // be extra defensive against nulls and bugs
+        HashMap<String, Serializable> ret = new HashMap<>();
+        if (allocator != null)
+        {
+            if (allocator.partitioner != null) ret.put("partitioner", 
allocator.partitioner.getClass().getSimpleName());
+            if (allocator.strategy != null) ret.put("strategy", 
allocator.strategy.getClass().getSimpleName());
+        }
+        ret.put("replicas", replicas);
+        ret.put("numTokens", this.numTokens);
+        ret.put("sortedUnits", String.valueOf(sortedUnits));
+        ret.put("sortedTokens", String.valueOf(sortedTokens));
+        ret.put("unitToTokens", String.valueOf(unitToTokens));
+        ret.put("tokens", String.valueOf(tokens));
+        ret.put("unit", String.valueOf(unit));
+        ret.put("tokenInfo", String.valueOf(tokenInfo));
+        return ret;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/dht/tokenallocator/TokenAllocatorFactory.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/dht/tokenallocator/TokenAllocatorFactory.java 
b/src/java/org/apache/cassandra/dht/tokenallocator/TokenAllocatorFactory.java
index 5fdba02..117fd09 100644
--- 
a/src/java/org/apache/cassandra/dht/tokenallocator/TokenAllocatorFactory.java
+++ 
b/src/java/org/apache/cassandra/dht/tokenallocator/TokenAllocatorFactory.java
@@ -37,9 +37,13 @@ public class TokenAllocatorFactory
         if(strategy.replicas() == 1)
         {
             logger.info("Using NoReplicationTokenAllocator.");
-            return new NoReplicationTokenAllocator<>(sortedTokens, strategy, 
partitioner);
+            NoReplicationTokenAllocator<InetAddressAndPort> allocator = new 
NoReplicationTokenAllocator<>(sortedTokens, strategy, partitioner);
+            
TokenAllocatorDiagnostics.noReplicationTokenAllocatorInstanciated(allocator);
+            return allocator;
         }
         logger.info("Using ReplicationAwareTokenAllocator.");
-        return new ReplicationAwareTokenAllocator<>(sortedTokens, strategy, 
partitioner);
+        ReplicationAwareTokenAllocator<InetAddressAndPort> allocator = new 
ReplicationAwareTokenAllocator<>(sortedTokens, strategy, partitioner);
+        
TokenAllocatorDiagnostics.replicationTokenAllocatorInstanciated(allocator);
+        return allocator;
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/diag/DiagnosticEvent.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/diag/DiagnosticEvent.java 
b/src/java/org/apache/cassandra/diag/DiagnosticEvent.java
new file mode 100644
index 0000000..5de703b
--- /dev/null
+++ b/src/java/org/apache/cassandra/diag/DiagnosticEvent.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.diag;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * Base class for internally emitted events used for diagnostics and testing.
+ */
+public abstract class DiagnosticEvent
+{
+    /**
+     * Event creation time.
+     */
+    public final long timestamp = System.currentTimeMillis();
+
+    /**
+     * Name of allocating thread.
+     */
+    public final String threadName = Thread.currentThread().getName();
+
+    /**
+     * Returns event type discriminator. This will usually be a enum value.
+     */
+    public abstract Enum<?> getType();
+
+    /**
+     * Returns map of key-value pairs containing relevant event details. 
Values can be complex objects like other
+     * maps, but must be Serializable, as returned values may be consumed by 
external clients. It's strongly recommended
+     * to stick to standard Java classes to avoid distributing custom classes 
to clients and also prevent potential
+     * class versioning conflicts.
+     */
+    public abstract Map<String, Serializable> toMap();
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/diag/DiagnosticEventService.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/diag/DiagnosticEventService.java 
b/src/java/org/apache/cassandra/diag/DiagnosticEventService.java
new file mode 100644
index 0000000..577d0ba
--- /dev/null
+++ b/src/java/org/apache/cassandra/diag/DiagnosticEventService.java
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.diag;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Iterables;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.config.DatabaseDescriptor;
+
+/**
+ * Service for publishing and consuming {@link DiagnosticEvent}s.
+ */
+public final class DiagnosticEventService
+{
+    private static final Logger logger = 
LoggerFactory.getLogger(DiagnosticEventService.class);
+
+    // Subscribers interested in consuming all kind of events
+    private ImmutableSet<Consumer<DiagnosticEvent>> subscribersAll = 
ImmutableSet.of();
+
+    // Subscribers for particular event class, e.g. BootstrapEvent
+    private ImmutableSetMultimap<Class<? extends DiagnosticEvent>, 
Consumer<DiagnosticEvent>> subscribersByClass = ImmutableSetMultimap.of();
+
+    // Subscribers for event class and type, e.g. 
BootstrapEvent#TOKENS_ALLOCATED
+    private ImmutableMap<Class, ImmutableSetMultimap<Enum<?>, 
Consumer<DiagnosticEvent>>> subscribersByClassAndType = ImmutableMap.of();
+
+    private static final DiagnosticEventService instance = new 
DiagnosticEventService();
+
+    private DiagnosticEventService()
+    {
+    }
+
+    /**
+     * Makes provided event available to all subscribers.
+     */
+    public void publish(DiagnosticEvent event)
+    {
+        if (!DatabaseDescriptor.diagnosticEventsEnabled())
+            return;
+
+        logger.trace("Publishing: {}", event);
+
+        // event class + type
+        ImmutableMultimap<Enum<?>, Consumer<DiagnosticEvent>> consumersByType 
= subscribersByClassAndType.get(event.getClass());
+        if (consumersByType != null)
+        {
+            ImmutableCollection<Consumer<DiagnosticEvent>> consumers = 
consumersByType.get(event.getType());
+            if (consumers != null)
+            {
+                for (Consumer<DiagnosticEvent> consumer : consumers)
+                    consumer.accept(event);
+            }
+        }
+
+        // event class
+        Set<Consumer<DiagnosticEvent>> consumersByEvents = 
subscribersByClass.get(event.getClass());
+        if (consumersByEvents != null)
+        {
+            for (Consumer<DiagnosticEvent> consumer : consumersByEvents)
+                consumer.accept(event);
+        }
+
+        // all events
+        for (Consumer<DiagnosticEvent> consumer : subscribersAll)
+            consumer.accept(event);
+    }
+
+    /**
+     * Registers event handler for specified class of events.
+     * @param event DiagnosticEvent class implementation
+     * @param consumer Consumer for received events
+     */
+    public synchronized <E extends DiagnosticEvent> void subscribe(Class<E> 
event, Consumer<E> consumer)
+    {
+        subscribersByClass = ImmutableSetMultimap.<Class<? extends 
DiagnosticEvent>, Consumer<DiagnosticEvent>>builder()
+                              .putAll(subscribersByClass)
+                              .put(event, new TypedConsumerWrapper<>(consumer))
+                              .build();
+    }
+
+    /**
+     * Registers event handler for specified class of events.
+     * @param event DiagnosticEvent class implementation
+     * @param consumer Consumer for received events
+     */
+    public synchronized <E extends DiagnosticEvent, T extends Enum<T>> void 
subscribe(Class<E> event,
+                                                                               
       T eventType,
+                                                                               
       Consumer<E> consumer)
+    {
+        ImmutableSetMultimap.Builder<Enum<?>, Consumer<DiagnosticEvent>> 
byTypeBuilder = ImmutableSetMultimap.builder();
+        if (subscribersByClassAndType.containsKey(event))
+            byTypeBuilder.putAll(subscribersByClassAndType.get(event));
+        byTypeBuilder.put(eventType, new TypedConsumerWrapper<>(consumer));
+
+        ImmutableMap.Builder<Class, ImmutableSetMultimap<Enum<?>, 
Consumer<DiagnosticEvent>>> byClassBuilder = ImmutableMap.builder();
+        for (Class clazz : subscribersByClassAndType.keySet())
+        {
+            if (!clazz.equals(event))
+                byClassBuilder.put(clazz, 
subscribersByClassAndType.get(clazz));
+        }
+
+        subscribersByClassAndType = byClassBuilder
+                                    .put(event, byTypeBuilder.build())
+                                    .build();
+    }
+
+    /**
+     * Registers event handler for all DiagnosticEvents published from this 
point.
+     * @param consumer Consumer for received events
+     */
+    public synchronized void subscribeAll(Consumer<DiagnosticEvent> consumer)
+    {
+        subscribersAll = ImmutableSet.<Consumer<DiagnosticEvent>>builder()
+                         .addAll(subscribersAll)
+                         .add(consumer)
+                         .build();
+    }
+
+    /**
+     * De-registers event handler from receiving any further events.
+     * @param consumer Consumer registered for receiving events
+     */
+    public synchronized <E extends DiagnosticEvent> void 
unsubscribe(Consumer<E> consumer)
+    {
+        // all events
+        subscribersAll = ImmutableSet.copyOf(Iterables.filter(subscribersAll, 
(c) -> c != consumer));
+
+        // event class
+        ImmutableSetMultimap.Builder<Class<? extends DiagnosticEvent>, 
Consumer<DiagnosticEvent>> byClassBuilder = ImmutableSetMultimap.builder();
+        Collection<Map.Entry<Class<? extends DiagnosticEvent>, 
Consumer<DiagnosticEvent>>> entries = subscribersByClass.entries();
+        for (Map.Entry<Class<? extends DiagnosticEvent>, 
Consumer<DiagnosticEvent>> entry : entries)
+        {
+            Consumer<DiagnosticEvent> subscriber = entry.getValue();
+            if (subscriber instanceof TypedConsumerWrapper)
+                subscriber = ((TypedConsumerWrapper)subscriber).wrapped;
+
+            if (subscriber != consumer)
+            {
+                byClassBuilder = byClassBuilder.put(entry);
+            }
+        }
+        subscribersByClass = byClassBuilder.build();
+
+
+        // event class + type
+        ImmutableMap.Builder<Class, ImmutableSetMultimap<Enum<?>, 
Consumer<DiagnosticEvent>>> byClassAndTypeBuilder = ImmutableMap.builder();
+        for (Map.Entry<Class, ImmutableSetMultimap<Enum<?>, 
Consumer<DiagnosticEvent>>> byClassEntry : subscribersByClassAndType.entrySet())
+        {
+            ImmutableSetMultimap.Builder<Enum<?>, Consumer<DiagnosticEvent>> 
byTypeBuilder = ImmutableSetMultimap.builder();
+            ImmutableSetMultimap<Enum<?>, Consumer<DiagnosticEvent>> 
byTypeConsumers = byClassEntry.getValue();
+            Iterables.filter(byTypeConsumers.entries(), (e) ->
+            {
+                if (e == null || e.getValue() == null) return false;
+                Consumer<DiagnosticEvent> subscriber = e.getValue();
+                if (subscriber instanceof TypedConsumerWrapper)
+                    subscriber = ((TypedConsumerWrapper) subscriber).wrapped;
+                return subscriber != consumer;
+            }).forEach(byTypeBuilder::put);
+
+            ImmutableSetMultimap<Enum<?>, Consumer<DiagnosticEvent>> byType = 
byTypeBuilder.build();
+            if (!byType.isEmpty())
+                byClassAndTypeBuilder.put(byClassEntry.getKey(), byType);
+        }
+
+        subscribersByClassAndType = byClassAndTypeBuilder.build();
+    }
+
+    /**
+     * Indicates if any {@link Consumer} has been registered for the specified 
class of events.
+     * @param event DiagnosticEvent class implementation
+     */
+    public <E extends DiagnosticEvent> boolean hasSubscribers(Class<E> event)
+    {
+        return !subscribersAll.isEmpty() || 
subscribersByClass.containsKey(event) || 
subscribersByClassAndType.containsKey(event);
+    }
+
+    /**
+     * Indicates if any {@link Consumer} has been registered for the specified 
class of events.
+     * @param event DiagnosticEvent class implementation
+     * @param eventType Subscribed event type matched against {@link 
DiagnosticEvent#getType()}
+     */
+    public <E extends DiagnosticEvent, T extends Enum<T>> boolean 
hasSubscribers(Class<E> event, T eventType)
+    {
+        if (!subscribersAll.isEmpty())
+            return true;
+
+        ImmutableSet<Consumer<DiagnosticEvent>> subscribers = 
subscribersByClass.get(event);
+        if (subscribers != null && !subscribers.isEmpty())
+            return true;
+
+        ImmutableSetMultimap<Enum<?>, Consumer<DiagnosticEvent>> byType = 
subscribersByClassAndType.get(event);
+        if (byType == null || byType.isEmpty()) return false;
+
+        Set<Consumer<DiagnosticEvent>> consumers = byType.get(eventType);
+        return consumers != null && !consumers.isEmpty();
+    }
+
+    /**
+     * Indicates if events are enabled for specified event class based on 
{@link DatabaseDescriptor#diagnosticEventsEnabled()}
+     * and {@link #hasSubscribers(Class)}.
+     * @param event DiagnosticEvent class implementation
+     */
+    public <E extends DiagnosticEvent> boolean isEnabled(Class<E> event)
+    {
+        return DatabaseDescriptor.diagnosticEventsEnabled() && 
hasSubscribers(event);
+    }
+
+    /**
+     * Indicates if events are enabled for specified event class based on 
{@link DatabaseDescriptor#diagnosticEventsEnabled()}
+     * and {@link #hasSubscribers(Class, Enum)}.
+     * @param event DiagnosticEvent class implementation
+     * @param eventType Subscribed event type matched against {@link 
DiagnosticEvent#getType()}
+     */
+    public <E extends DiagnosticEvent, T extends Enum<T>> boolean 
isEnabled(Class<E> event, T eventType)
+    {
+        return DatabaseDescriptor.diagnosticEventsEnabled() && 
hasSubscribers(event, eventType);
+    }
+
+    public static DiagnosticEventService instance()
+    {
+        return instance;
+    }
+
+    /**
+     * Removes all active subscribers. Should only be called from testing.
+     */
+    public synchronized void cleanup()
+    {
+        subscribersByClass = ImmutableSetMultimap.of();
+        subscribersAll = ImmutableSet.of();
+        subscribersByClassAndType = ImmutableMap.of();
+    }
+
+    /**
+     * Wrapper class for supporting typed event handling for consumers.
+     */
+    private static class TypedConsumerWrapper<E> implements 
Consumer<DiagnosticEvent>
+    {
+        private final Consumer<E> wrapped;
+
+        private TypedConsumerWrapper(Consumer<E> wrapped)
+        {
+            this.wrapped = wrapped;
+        }
+
+        public void accept(DiagnosticEvent e)
+        {
+            wrapped.accept((E)e);
+        }
+
+        public boolean equals(Object o)
+        {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            TypedConsumerWrapper<?> that = (TypedConsumerWrapper<?>) o;
+            return Objects.equals(wrapped, that.wrapped);
+        }
+
+        public int hashCode()
+        {
+            return Objects.hash(wrapped);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/gms/Gossiper.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/gms/Gossiper.java 
b/src/java/org/apache/cassandra/gms/Gossiper.java
index 7bb2583..170843b 100644
--- a/src/java/org/apache/cassandra/gms/Gossiper.java
+++ b/src/java/org/apache/cassandra/gms/Gossiper.java
@@ -381,6 +381,8 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean
         {
             markDead(endpoint, epState);
         }
+
+        GossiperDiagnostics.convicted(this, endpoint, phi);
     }
 
     /**
@@ -398,6 +400,7 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean
         epState.getHeartBeatState().forceHighestPossibleVersionUnsafe();
         markDead(endpoint, epState);
         FailureDetector.instance.forceConviction(endpoint);
+        GossiperDiagnostics.markedAsShutdown(this, endpoint);
     }
 
     /**
@@ -428,6 +431,7 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean
         quarantineEndpoint(endpoint);
         if (logger.isDebugEnabled())
             logger.debug("evicting {} from gossip", endpoint);
+        GossiperDiagnostics.evictedFromMembership(this, endpoint);
     }
 
     /**
@@ -453,6 +457,7 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean
         MessagingService.instance().destroyConnectionPool(endpoint);
         if (logger.isDebugEnabled())
             logger.debug("removing endpoint {}", endpoint);
+        GossiperDiagnostics.removedEndpoint(this, endpoint);
     }
 
     /**
@@ -474,6 +479,7 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean
     private void quarantineEndpoint(InetAddressAndPort endpoint, long 
quarantineExpiration)
     {
         justRemovedEndpoints.put(endpoint, quarantineExpiration);
+        GossiperDiagnostics.quarantinedEndpoint(this, endpoint, 
quarantineExpiration);
     }
 
     /**
@@ -485,6 +491,7 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean
         // remember, quarantineEndpoint will effectively already add 
QUARANTINE_DELAY, so this is 2x
         logger.debug("");
         quarantineEndpoint(endpoint, System.currentTimeMillis() + 
QUARANTINE_DELAY);
+        GossiperDiagnostics.replacementQuarantine(this, endpoint);
     }
 
     /**
@@ -498,6 +505,7 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean
         removeEndpoint(endpoint);
         evictFromMembership(endpoint);
         replacementQuarantine(endpoint);
+        GossiperDiagnostics.replacedEndpoint(this, endpoint);
     }
 
     /**
@@ -688,7 +696,10 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean
         if (firstSynSendAt == 0)
             firstSynSendAt = System.nanoTime();
         MessagingService.instance().sendOneWay(message, to);
-        return seeds.contains(to);
+
+        boolean isSeed = seeds.contains(to);
+        GossiperDiagnostics.sendGossipDigestSyn(this, to);
+        return isSeed;
     }
 
     /* Sends a Gossip message to a live member and returns true if the 
recipient was a seed */
@@ -889,6 +900,31 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean
         return endpointStateMap.size();
     }
 
+    Map<InetAddressAndPort, EndpointState> getEndpointStateMap()
+    {
+        return ImmutableMap.copyOf(endpointStateMap);
+    }
+
+    Map<InetAddressAndPort, Long> getJustRemovedEndpoints()
+    {
+        return ImmutableMap.copyOf(justRemovedEndpoints);
+    }
+
+    Map<InetAddressAndPort, Long> getUnreachableEndpoints()
+    {
+        return ImmutableMap.copyOf(unreachableEndpoints);
+    }
+
+    Set<InetAddressAndPort> getSeedsInShadowRound()
+    {
+        return ImmutableSet.copyOf(seedsInShadowRound);
+    }
+
+    long getLastProcessedMessageAt()
+    {
+        return lastProcessedMessageAt;
+    }
+
     public UUID getHostId(InetAddressAndPort endpoint)
     {
         return getHostId(endpoint, endpointStateMap);
@@ -1028,6 +1064,8 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean
         };
 
         MessagingService.instance().sendRR(echoMessage, addr, echoHandler);
+
+        GossiperDiagnostics.markedAlive(this, addr, localState);
     }
 
     @VisibleForTesting
@@ -1046,6 +1084,8 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean
             subscriber.onAlive(addr, localState);
         if (logger.isTraceEnabled())
             logger.trace("Notified {}", subscribers);
+
+        GossiperDiagnostics.realMarkedAlive(this, addr, localState);
     }
 
     @VisibleForTesting
@@ -1061,6 +1101,8 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean
             subscriber.onDead(addr, localState);
         if (logger.isTraceEnabled())
             logger.trace("Notified {}", subscribers);
+
+        GossiperDiagnostics.markedDead(this, addr, localState);
     }
 
     /**
@@ -1101,6 +1143,8 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean
         // check this at the end so nodes will learn about the endpoint
         if (isShutdown(ep))
             markAsShutdown(ep);
+
+        GossiperDiagnostics.majorStateChangeHandled(this, ep, epState);
     }
 
     public boolean isAlive(InetAddressAndPort endpoint)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/2846b22a/src/java/org/apache/cassandra/gms/GossiperDiagnostics.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/gms/GossiperDiagnostics.java 
b/src/java/org/apache/cassandra/gms/GossiperDiagnostics.java
new file mode 100644
index 0000000..57552cc
--- /dev/null
+++ b/src/java/org/apache/cassandra/gms/GossiperDiagnostics.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.gms;
+
+
+import org.apache.cassandra.diag.DiagnosticEventService;
+import org.apache.cassandra.gms.GossiperEvent.GossiperEventType;
+import org.apache.cassandra.locator.InetAddressAndPort;
+
+/**
+ * Utility methods for DiagnosticEvent activities.
+ */
+final class GossiperDiagnostics
+{
+    private static final DiagnosticEventService service = 
DiagnosticEventService.instance();
+
+    private GossiperDiagnostics()
+    {
+    }
+
+    static void markedAsShutdown(Gossiper gossiper, InetAddressAndPort 
endpoint)
+    {
+        if (isEnabled(GossiperEventType.MARKED_AS_SHUTDOWN))
+            service.publish(new 
GossiperEvent(GossiperEventType.MARKED_AS_SHUTDOWN, gossiper, endpoint, null, 
null));
+    }
+
+    static void convicted(Gossiper gossiper, InetAddressAndPort endpoint, 
double phi)
+    {
+        if (isEnabled(GossiperEventType.CONVICTED))
+            service.publish(new GossiperEvent(GossiperEventType.CONVICTED, 
gossiper, endpoint, null, null));
+    }
+
+    static void replacementQuarantine(Gossiper gossiper, InetAddressAndPort 
endpoint)
+    {
+        if (isEnabled(GossiperEventType.REPLACEMENT_QUARANTINE))
+            service.publish(new 
GossiperEvent(GossiperEventType.REPLACEMENT_QUARANTINE, gossiper, endpoint, 
null, null));
+    }
+
+    static void replacedEndpoint(Gossiper gossiper, InetAddressAndPort 
endpoint)
+    {
+        if (isEnabled(GossiperEventType.REPLACED_ENDPOINT))
+            service.publish(new 
GossiperEvent(GossiperEventType.REPLACED_ENDPOINT, gossiper, endpoint, null, 
null));
+    }
+
+    static void evictedFromMembership(Gossiper gossiper, InetAddressAndPort 
endpoint)
+    {
+        if (isEnabled(GossiperEventType.EVICTED_FROM_MEMBERSHIP))
+            service.publish(new 
GossiperEvent(GossiperEventType.EVICTED_FROM_MEMBERSHIP, gossiper, endpoint, 
null, null));
+    }
+
+    static void removedEndpoint(Gossiper gossiper, InetAddressAndPort endpoint)
+    {
+        if (isEnabled(GossiperEventType.REMOVED_ENDPOINT))
+            service.publish(new 
GossiperEvent(GossiperEventType.REMOVED_ENDPOINT, gossiper, endpoint, null, 
null));
+    }
+
+    static void quarantinedEndpoint(Gossiper gossiper, InetAddressAndPort 
endpoint, long quarantineExpiration)
+    {
+        if (isEnabled(GossiperEventType.QUARANTINED_ENDPOINT))
+            service.publish(new 
GossiperEvent(GossiperEventType.QUARANTINED_ENDPOINT, gossiper, endpoint, 
quarantineExpiration, null));
+    }
+
+    static void markedAlive(Gossiper gossiper, InetAddressAndPort addr, 
EndpointState localState)
+    {
+        if (isEnabled(GossiperEventType.MARKED_ALIVE))
+            service.publish(new GossiperEvent(GossiperEventType.MARKED_ALIVE, 
gossiper, addr, null, localState));
+    }
+
+    static void realMarkedAlive(Gossiper gossiper, InetAddressAndPort addr, 
EndpointState localState)
+    {
+        if (isEnabled(GossiperEventType.REAL_MARKED_ALIVE))
+            service.publish(new 
GossiperEvent(GossiperEventType.REAL_MARKED_ALIVE, gossiper, addr, null, 
localState));
+    }
+
+    static void markedDead(Gossiper gossiper, InetAddressAndPort addr, 
EndpointState localState)
+    {
+        if (isEnabled(GossiperEventType.MARKED_DEAD))
+            service.publish(new GossiperEvent(GossiperEventType.MARKED_DEAD, 
gossiper, addr, null, localState));
+    }
+
+    static void majorStateChangeHandled(Gossiper gossiper, InetAddressAndPort 
addr, EndpointState state)
+    {
+        if (isEnabled(GossiperEventType.MAJOR_STATE_CHANGE_HANDLED))
+            service.publish(new 
GossiperEvent(GossiperEventType.MAJOR_STATE_CHANGE_HANDLED, gossiper, addr, 
null, state));
+    }
+
+    static void sendGossipDigestSyn(Gossiper gossiper, InetAddressAndPort to)
+    {
+        if (isEnabled(GossiperEventType.SEND_GOSSIP_DIGEST_SYN))
+            service.publish(new 
GossiperEvent(GossiperEventType.SEND_GOSSIP_DIGEST_SYN, gossiper, to, null, 
null));
+    }
+
+    private static boolean isEnabled(GossiperEventType type)
+    {
+        return service.isEnabled(GossiperEvent.class, type);
+    }
+}
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to