<!--
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.
-->

## Metrics

### Quick overview

* `advanced.metrics` in the configuration. All metrics disabled by default. To enable, select the
  metrics library to use, then define which individual metrics to activate.
* some metrics are per node, others global to the session, or both.
* unlike driver 3, JMX is not provided out of the box. You need to add the dependency manually.

-----

The driver is able to report measurements of its internal behavior to a variety of metrics
libraries, and ships with bindings for three popular ones: [Dropwizard Metrics] , [Micrometer
Metrics] and [MicroProfile Metrics].

### Selecting a Metrics Library

#### Dropwizard Metrics

Dropwizard is the driver's default metrics library; there is no additional configuration nor any
extra dependency to add if you wish to use Dropwizard.

#### Micrometer

To use Micrometer you must:

1. Define `MicrometerMetricsFactory` as the metrics factory to use in the driver configuration: 

```
datastax-java-driver.advanced.metrics {
  factory.class = MicrometerMetricsFactory
}
```

2. Add a dependency to `java-driver-metrics-micrometer` in your application. This separate driver
module contains the actual bindings for Micrometer, and depends itself on the Micrometer library:

```xml
<dependency>
  <groupId>org.apache.cassandra</groupId>
  <artifactId>java-driver-metrics-micrometer</artifactId>
  <version>${driver.version}</version>
</dependency>
```

3. You should also exclude Dropwizard and HdrHistogram, which are two transitive dependencies of the
driver, because they are not relevant when using Micrometer:

```xml
<dependency>
  <groupId>org.apache.cassandra</groupId>
  <artifactId>java-driver-core</artifactId>
  <exclusions>
    <exclusion>
      <groupId>io.dropwizard.metrics</groupId>
      <artifactId>metrics-core</artifactId>
    </exclusion>
    <exclusion>
      <groupId>org.hdrhistogram</groupId>
      <artifactId>HdrHistogram</artifactId>
    </exclusion>
  </exclusions>
</dependency>
```

#### MicroProfile Metrics

To use MicroProfile Metrics you must:

1. Define `MicroProfileMetricsFactory` as the metrics factory to use in the driver configuration:

```
datastax-java-driver.advanced.metrics {
  factory.class = MicroProfileMetricsFactory
}
```

2. Add a dependency to `java-driver-metrics-microprofile` in your application. This separate driver
module contains the actual bindings for MicroProfile, and depends itself on the MicroProfile Metrics
library:

```xml
<dependency>
  <groupId>org.apache.cassandra</groupId>
  <artifactId>java-driver-metrics-microprofile</artifactId>
  <version>${driver.version}</version>
</dependency>
```

3. You should also exclude Dropwizard and HdrHistogram, which are two transitive dependencies of the
driver, because they are not relevant when using MicroProfile Metrics:

```xml
<dependency>
  <groupId>org.apache.cassandra</groupId>
  <artifactId>java-driver-core</artifactId>
  <exclusions>
    <exclusion>
      <groupId>io.dropwizard.metrics</groupId>
      <artifactId>metrics-core</artifactId>
    </exclusion>
    <exclusion>
      <groupId>org.hdrhistogram</groupId>
      <artifactId>HdrHistogram</artifactId>
    </exclusion>
  </exclusions>
</dependency>
```

#### Other Metrics libraries

Other metrics libraries can also be used. However, you will need to provide a custom
metrics factory. Simply implement the
`com.datastax.oss.driver.internal.core.metrics.MetricsFactory` interface for your library of choice,
then pass the fully-qualified name of that implementation class to the driver using the
`advanced.metrics.factory.class` option. See the [reference configuration].

You will certainly need to add the metrics library as a dependency to your application as well.
It is also recommended excluding Dropwizard and HdrHistogram, as shown above.

### Enabling specific driver metrics

Now that the metrics library is configured, you need to activate the driver metrics you are
interested in.

There are two categories of driver metrics:

* session-level: the measured data is global to a `Session` instance. For example, `connected-nodes`
  measures the number of nodes to which we have connections.
* node-level: the data is specific to a node (and therefore there is one metric instance per node).
  For example, `pool.open-connections` measures the number of connections open to this particular
  node.

To find out which metrics are available, see the [reference configuration]. It contains a
commented-out line for each metric, with detailed explanations on its intended usage.

By default, all metrics are disabled. You can turn them on individually in the configuration, by
adding their name to these lists:

```
datastax-java-driver.advanced.metrics {
  session.enabled = [ connected-nodes, cql-requests ]
  node.enabled = [ pool.open-connections, pool.in-flight ]
}
```

If you specify a metric that doesn't exist, it will be ignored, and a warning will be logged.

Finally, if you are using Dropwizard or Micrometer and enabled any metric of timer type, such as
`cql-requests`, it is also possible to provide additional configuration to fine-tune the underlying
histogram's characteristics and precision, such as its highest expected latency, its number of
significant digits to use, and its refresh interval. Again, see the [reference configuration] for
more details.

### Selecting a metric identifier style

Most metric libraries uniquely identify a metric by a name and, optionally, by a set of key-value
pairs, usually called tags.

The `advanced.metrics.id-generator.class` option is used to customize how the driver generates
metric identifiers. The driver ships with two built-in implementations:

- `DefaultMetricIdGenerator`: generates identifiers composed solely of (unique) metric names; it
  does not generate tags. All metric names start with the name of the session (see `session-name` in
  the configuration), and in the case of node-level metrics, this is followed by `.nodes.`, followed
  by a textual representation of the node's address. All names end with the metric distinctive name.
  See below for examples. This generator is mostly suitable for use with metrics libraries that do
  not support tags, like Dropwizard.

- `TaggingMetricIdGenerator`: generates identifiers composed of a name and one or two tags.
  Session-level metric names start with the `session.` prefix followed by the metric distinctive
  name; node-level metric names start with the `nodes.` prefix followed by the metric distinctive
  name. Session-level tags will include a `session` tag whose value is the session name (see
  `session-name` in the configuration); node-level tags will include the same `session` tag, and
  also a `node` tag whose value is the node's address. See below for examples. This generator is
  mostly suitable for use with metrics libraries that support tags, like Micrometer or MicroProfile
  Metrics.

For example, here is how each one of them generates identifiers for the session metric "bytes-sent",
assuming that the session is named "s0":

- `DefaultMetricIdGenerator`:
  - name:`s0.bytes-sent`
  - tags: `{}`
- `TaggingMetricIdGenerator`:
  - name: `session.bytes-sent`
  - tags: `{ "session" : "s0" }`

Here is how each one of them generates identifiers for the node metric "bytes-sent", assuming that
the session is named "s0", and the node's broadcast address is 10.1.2.3:9042:

- `DefaultMetricIdGenerator`:
  - name : `s0.nodes.10_1_2_3:9042.bytes-sent`
  - tags: `{}`
- `TaggingMetricIdGenerator`:
  - name `nodes.bytes-sent`
  - tags: `{ "session" : "s0", "node" : "\10.1.2.3:9042" }`

As shown above, both built-in implementations generate names that are path-like structures separated
by dots. This is indeed the most common expected format by reporting tools.

Finally, it is also possible to define a global prefix for all metric names; this can be done with
the `advanced.metrics.id-generator.prefix` option.

The prefix should not start nor end with a dot or any other path separator; the following are two
valid examples: `cassandra` or `myapp.prod.cassandra`.

For example, if this prefix is set to `cassandra`, here is how the session metric "bytes-sent" would
be named, assuming that the session is named "s0":

- with `DefaultMetricIdGenerator`: `cassandra.s0.bytes-sent`
- with `TaggingMetricIdGenerator`: `cassandra.session.bytes-sent`

Here is how the node metric "bytes-sent" would be named, assuming that the session is named "s0",
and the node's broadcast address is 10.1.2.3:9042:

- with `DefaultMetricIdGenerator`: `cassandra.s0.nodes.10_1_2_3:9042.bytes-sent`
- with `TaggingMetricIdGenerator`: `cassandra.nodes.bytes-sent`

### Using an external metric registry

Regardless of which metrics library is used, you can provide an external metric registry object when
building a session. This allows the driver to transparently export its operational metrics to
whatever reporting system you want to use.

To pass a metric registry object to the session, use the `CqlSessionBuilder.withMetricRegistry()`
method:

```java
CqlSessionBuilder builder = CqlSession.builder();
builder.withMetricRegistry(myRegistryObject);
CqlSession session = builder.build();
```

Beware that the driver does not inspect the provided object, it simply passes it to the metrics
factory in use; it is the user's responsibility to provide registry objects compatible with the
metrics library in use. For reference, here are the expected base types for the three built-in
metrics libraries:

* Dropwizard:   `com.codahale.metrics.MetricRegistry`
* Micrometer:   `io.micrometer.core.instrument.MeterRegistry`
* MicroProfile: `org.eclipse.microprofile.metrics.MetricRegistry`

**NOTE:** MicroProfile **requires** an external instance of its registry to be provided. For
Micrometer, if no registry object is provided, Micrometer's `globalRegistry` will be used. For
Dropwizard, if no registry object is provided, an instance of `MetricRegistry` will be created and
used (in which case, it can be retrieved programmatically if needed, see below).

### Programmatic access to driver metrics

Programmatic access to driver metrics is only available when using Dropwizard Metrics. Users of
other libraries are encouraged to provide an external registry when creating the driver session (see
above), then use it to gain programmatic access to the driver metrics.

The Dropwizard `MetricRegistry` object is exposed in the driver API via
`session.getMetrics().getRegistry()`. You can retrieve it and, for example, configure a `Reporter`
to send the metrics to a monitoring tool.

**NOTE:** Beware that `session.getMetrics()` is not available when using other metrics libraries,
and will throw a `NoClassDefFoundError` at runtime if accessed in such circumstances.

### Exposing driver metrics with JMX

Unlike previous driver versions, JMX support is not included out of the box.

The way to add JMX support to your application depends largely on the metrics library being used. We
show below instructions for Dropwizard only. Micrometer also has support for JMX: please refer to
its [official documentation][Micrometer JMX].

#### Dropwizard Metrics

Add the following dependency to your application (make sure the version matches the `metrics-core`
dependency of the driver):

```xml
<dependency>
  <groupId>io.dropwizard.metrics</groupId>
  <artifactId>metrics-jmx</artifactId>
  <version>4.1.2</version>
</dependency>
```

Then create a JMX reporter for the registry:

```java
MetricRegistry registry = session.getMetrics()
    .orElseThrow(() -> new IllegalStateException("Metrics are disabled"))
    .getRegistry();

JmxReporter reporter =
    JmxReporter.forRegistry(registry)
        .inDomain("com.datastax.oss.driver")
        .build();
reporter.start();
```

Note: by default, the JMX reporter exposes all metrics in a flat structure (for example,
`pool.open-connections` and `pool.in-flight` appear as root elements). If you prefer a hierarchical
structure (`open-connections` and `in-flight` nested into a `pool` sub-domain), use a custom object
factory:

```java
import com.codahale.metrics.jmx.JmxReporter;
import com.codahale.metrics.jmx.ObjectNameFactory;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

ObjectNameFactory objectNameFactory = (type, domain, name) -> {
  StringBuilder objectName = new StringBuilder(domain).append(':');
  List<String> nameParts = Splitter.on('.').splitToList(name);
  int i = 0;
  for (String namePart : nameParts) {
    boolean isLast = (i == nameParts.size() - 1);
    String key =
        isLast ? "name" : Strings.padStart(Integer.toString(i), 2, '0');
    objectName.append(key).append('=').append(namePart);
    if (!isLast) {
      objectName.append(',');
    }
    i += 1;
  }
  try {
    return new ObjectName(objectName.toString());
  } catch (MalformedObjectNameException e) {
    throw new RuntimeException(e);
  }
};

JmxReporter reporter =
    JmxReporter.forRegistry(registry)
        .inDomain("com.datastax.oss.driver")
        .createsObjectNamesWith(objectNameFactory)
        .build();
reporter.start();
```

### Exporting metrics with other protocols

Dropwizard Metrics has built-in reporters for other output formats: JSON (via a servlet), stdout,
CSV files, SLF4J logs and Graphite. Refer to their [manual][Dropwizard manual] for more details.

[Dropwizard Metrics]: https://metrics.dropwizard.io/4.1.2
[Dropwizard Manual]: https://metrics.dropwizard.io/4.1.2/getting-started.html
[Micrometer Metrics]: https://micrometer.io/docs
[Micrometer JMX]: https://micrometer.io/docs/registry/jmx
[MicroProfile Metrics]: https://github.com/eclipse/microprofile-metrics
[reference configuration]: ../configuration/reference/README.md
