Skip to content

DOCSP-42300: Standardize Monitoring page #82

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 10, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions source/includes/monitoring/JMXMonitoring.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.example;

import com.mongodb.ConnectionString;
import com.mongodb.management.JMXConnectionPoolListener;
import com.mongodb.reactivestreams.client.*;

public class JMXMonitoring {

private static final ConnectionString URI = new ConnectionString("<connection string URI>");

public static void main(String[] args) throws InterruptedException {
// start-jmx-example
JMXConnectionPoolListener connectionPoolListener = new JMXConnectionPoolListener();

try (MongoClient mongoClient = MongoClients.create(URI)) {
System.out.println("Navigate to JConsole to see your connection pools...");

// Pauses the code execution so you can navigate to JConsole and inspect your connection pools
Thread.sleep(Long.MAX_VALUE);
}
// end-jmx-example
}
}

142 changes: 142 additions & 0 deletions source/includes/monitoring/Monitoring.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package org.example;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;

import com.mongodb.event.*;
import com.mongodb.reactivestreams.client.*;
import org.bson.Document;
import reactor.core.publisher.Flux;

import java.util.HashMap;
import java.util.Map;

public class Monitoring {

private static final String COLLECTION = "test_collection";
private static final String DATABASE = "test_db";
private static final ConnectionString URI = new ConnectionString("<connection string URI>");

public static void main(String[] args) {
Monitoring examples = new Monitoring();
System.out.println("\n---Command Event---\n");
examples.monitorCommandEvent();
System.out.println("\n---Cluster Event---\n");
examples.monitorClusterEvent();
System.out.println("\n---Connection Pool Event---\n");
examples.monitorConnectionPoolEvent();
}

private void monitorCommandEvent() {
// start-monitor-command-example
MongoClientSettings settings = MongoClientSettings.builder()
.applyConnectionString(URI)
.addCommandListener(new CommandCounter())
.build();

try (MongoClient mongoClient = MongoClients.create(settings)) {
MongoDatabase database = mongoClient.getDatabase(DATABASE);
MongoCollection<Document> collection = database.getCollection(COLLECTION);

// Run some commands to test the counter
FindPublisher<Document> findPublisher1 = collection.find();
FindPublisher<Document> findPublisher2 = collection.find();
Flux.from(findPublisher1).blockLast();
Flux.from(findPublisher2).blockLast();
}
// end-monitor-command-example
}

private void monitorClusterEvent() {
// start-monitor-cluster-example
MongoClientSettings settings = MongoClientSettings.builder()
.applyConnectionString(URI)
.applyToClusterSettings(builder ->
builder.addClusterListener(new IsWritable()))
.build();

try (MongoClient mongoClient = MongoClients.create(settings)) {
MongoDatabase database = mongoClient.getDatabase(DATABASE);
MongoCollection<Document> collection = database.getCollection(COLLECTION);

// Run a command to trigger a ClusterDescriptionChangedEvent
FindPublisher<Document> findPublisher = collection.find();
Flux.from(findPublisher).blockLast();
}
// end-monitor-cluster-example
}

private void monitorConnectionPoolEvent() {
// start-monitor-connection-pool-example
MongoClientSettings settings = MongoClientSettings.builder()
.applyConnectionString(URI)
.applyToConnectionPoolSettings(builder ->
builder.addConnectionPoolListener(new ConnectionPoolLibrarian()))
.build();

try (MongoClient mongoClient = MongoClients.create(settings)) {
MongoDatabase database = mongoClient.getDatabase(DATABASE);
MongoCollection<Document> collection = database.getCollection(COLLECTION);

// Run a command to trigger connection pool events
FindPublisher<Document> findPublisher = collection.find();
Flux.from(findPublisher).blockLast();
}
// end-monitor-connection-pool-example
}
}

// start-command-listener
class CommandCounter implements CommandListener {
private final Map<String, Integer> commands = new HashMap<String, Integer>();

@Override
public synchronized void commandSucceeded(final CommandSucceededEvent event) {
String commandName = event.getCommandName();
int count = commands.getOrDefault(commandName, 0);
commands.put(commandName, count + 1);
System.out.println(commands);
}

@Override
public void commandFailed(final CommandFailedEvent event) {
System.out.printf("Failed execution of command '%s' with id %s%n",
event.getCommandName(),
event.getRequestId());
}
}
// end-command-listener

// start-cluster-listener
class IsWritable implements ClusterListener {
private boolean isWritable;

@Override
public synchronized void clusterDescriptionChanged(final ClusterDescriptionChangedEvent event) {
if (!isWritable) {
if (event.getNewDescription().hasWritableServer()) {
isWritable = true;
System.out.println("Able to write to server");
}
} else if (!event.getNewDescription().hasWritableServer()) {
isWritable = false;
System.out.println("Unable to write to server");
}
}
}
// end-cluster-listener

// start-connection-pool-listener
class ConnectionPoolLibrarian implements ConnectionPoolListener {
@Override
public void connectionCheckedOut(final ConnectionCheckedOutEvent event) {
System.out.printf("Fetching the connection with id %s...%n",
event.getConnectionId().getLocalValue());
}

@Override
public void connectionCheckOutFailed(final ConnectionCheckOutFailedEvent event) {
System.out.println("Something went wrong! Failed to checkout connection.");
}
}
// end-connection-pool-listener
680 changes: 416 additions & 264 deletions source/monitoring.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
.. _javars-monitoring:
.. _java-rs-monitoring:

==============
JMX Monitoring
==============
=======================
Monitor your Deployment
=======================

.. facet::
:name: genre
@@ -17,263 +17,415 @@ JMX Monitoring
:depth: 2
:class: singlecol

The driver uses `JMX
<https://docs.oracle.com/javase/8/docs/technotes/guides/jmx/>`__ to
create `MXBeans <https://docs.oracle.com/javase/tutorial/jmx/mbeans/mxbeans.html>`__
that allow you to monitor various aspects of the driver.

The driver creates ``MXBean`` instances of a single
type, ``ConnectionPoolStatisticsMBean``. The driver registers one
``ConnectionPoolStatisticsMBean`` instance for each server it connects
to. For example, when connected to a replica set, the driver creates an
instance for each non-hidden member of the replica set.

Each ``MXBean`` instance is required to be registered with a unique object
name, which consists of a domain and a set of named properties. All
``MXBean`` instances created by the driver are under the domain
``org.mongodb.driver``. Instances of ``ConnectionPoolStatisticsMBean``
have the following properties:

- ``clusterId``: a client-generated unique identifier, required to
ensure object name uniqueness in situations where an application has
multiple ``MongoClient`` instances connected to the same MongoDB server
deployment
- ``host``: the hostname of the server
- ``port``: the port on which the server is listening
- ``minSize``: the minimum allowed size of the pool, including idle and
in-use members
- ``maxSize``: the maximum allowed size of the pool, including idle and
in-use members
- ``size``: the current size of the pool, including idle and in-use
members
- ``checkedOutCount``: the current count of connections that are
currently in use

JMX connection pool monitoring is disabled by default. To enable it
add a ``com.mongodb.management.JMXConnectionPoolListener`` instance
when creating a ``MongoClientSettings`` instance:

.. code-block:: java

MongoClientSettings settings =
MongoClientSettings.builder()
.applyToConnectionPoolSettings(builder -> builder.addConnectionPoolListener(new JMXConnectionPoolListener()))
.build();

Command Monitoring
------------------

The driver implements the command monitoring specification, allowing
an application to be notified when a command starts and when it either
succeeds or fails.

An application registers command listeners with a ``MongoClient`` by
configuring a ``MongoClientSettings`` instance with instances of classes
that implement the ``CommandListener`` interface. The following example
is a simple implementation of the ``CommandListener`` interface:

.. code-block:: java

public class TestCommandListener implements CommandListener {
@Override
public void commandStarted(final CommandStartedEvent event) {
System.out.println(String.format("Sent command '%s:%s' with id %s to database '%s' "
+ "on connection '%s' to server '%s'",
event.getCommandName(),
event.getCommand().get(event.getCommandName()),
event.getRequestId(),
event.getDatabaseName(),
event.getConnectionDescription()
.getConnectionId(),
event.getConnectionDescription().getServerAddress()));
}

@Override
public void commandSucceeded(final CommandSucceededEvent event) {
System.out.println(String.format("Successfully executed command '%s' with id %s "
+ "on connection '%s' to server '%s'",
event.getCommandName(),
event.getRequestId(),
event.getConnectionDescription()
.getConnectionId(),
event.getConnectionDescription().getServerAddress()));
}

@Override
public void commandFailed(final CommandFailedEvent event) {
System.out.println(String.format("Failed execution of command '%s' with id %s "
+ "on connection '%s' to server '%s' with exception '%s'",
event.getCommandName(),
event.getRequestId(),
event.getConnectionDescription()
.getConnectionId(),
event.getConnectionDescription().getServerAddress(),
event.getThrowable()));
}
}

The following example creates an instance of ``MongoClientSettings``
configured with an instance of ``TestCommandListener``:

.. code-block:: java

MongoClientSettings settings = MongoClientSettings.builder()
.addCommandListener(new TestCommandListener())
.build();
MongoClient client = MongoClients.create(settings);

A ``MongoClient`` configured with these options prints a message to
``System.out`` before sending each command to a MongoDB server, and
prints another message upon either successful completion or failure of each
command.

Cluster Monitoring
------------------

The driver implements the SDAM Monitoring specification, allowing an
application to be notified when the driver detects changes to the
topology of the MongoDB cluster to which it is connected.

An application registers listeners with a ``MongoClient`` by configuring
``MongoClientSettings`` with instances of classes that implement any of
the ``ClusterListener``, ``ServerListener``, or
``ServerMonitorListener`` interfaces.

The following code demonstrates how to create a cluster listener:

.. code-block:: java

public class TestClusterListener implements ClusterListener {
private final ReadPreference readPreference;
private boolean isWritable;
private boolean isReadable;

public TestClusterListener(final ReadPreference readPreference) {
this.readPreference = readPreference;
}

@Override
public void clusterOpening(final ClusterOpeningEvent clusterOpeningEvent) {
System.out.println(String.format("Cluster with unique client identifier %s opening",
clusterOpeningEvent.getClusterId()));
}

@Override
public void clusterClosed(final ClusterClosedEvent clusterClosedEvent) {
System.out.println(String.format("Cluster with unique client identifier %s closed",
clusterClosedEvent.getClusterId()));
}

@Override
public void clusterDescriptionChanged(final ClusterDescriptionChangedEvent event) {
if (!isWritable) {
if (event.getNewDescription().hasWritableServer()) {
isWritable = true;
System.out.println("Writable server available!");
}
} else {
if (!event.getNewDescription().hasWritableServer()) {
isWritable = false;
System.out.println("No writable server available!");
}
}

if (!isReadable) {
if (event.getNewDescription().hasReadableServer(readPreference)) {
isReadable = true;
System.out.println("Readable server available!");
}
} else {
if (!event.getNewDescription().hasReadableServer(readPreference)) {
isReadable = false;
System.out.println("No readable server available!");
}
}
}
}

The following example creates an instance of ``MongoClientSettings``
configured with an instance of ``TestClusterListener``:

.. code-block:: java

List<ServerAddress> seedList = ...
MongoClientSettings settings = MongoClientSettings.builder()
.applyToClusterSettings(builder ->
builder.addClusterListener(new TestClusterListener(ReadPreference.secondary())))
.build();
MongoClient client = MongoClients.create(settings);

A ``MongoClient`` configured with these options prints a message to
``System.out`` when the ``MongoClient`` is created with these options, and
when that ``MongoClient`` is closed. In addition, it prints a message
when the client enters any of the following states:

- Has an available server that will accept writes
- Is without an available server that will accept writes
- Has an available server that will accept reads by using the configured
``ReadPreference``
- Is without an available server that will accept reads by using the
configured ``ReadPreference``

Connection Pool Monitoring
--------------------------

The driver supports monitoring of connection pool-related events.

An application registers listeners with a ``MongoClient`` by configuring
``MongoClientSettings`` with instances of classes that implement the
``ConnectionPoolListener`` interface.

The following code demonstrates how to create a connection pool listener:

.. code-block:: java

public class TestConnectionPoolListener implements ConnectionPoolListener {
@Override
public void connectionPoolOpened(final ConnectionPoolOpenedEvent event) {
System.out.println(event);
}

@Override
public void connectionPoolClosed(final ConnectionPoolClosedEvent event) {
System.out.println(event);
}

@Override
public void connectionCheckedOut(final ConnectionCheckedOutEvent event) {
System.out.println(event);
}

@Override
public void connectionCheckedIn(final ConnectionCheckedInEvent event) {
System.out.println(event);
}

@Override
public void connectionAdded(final ConnectionAddedEvent event) {
System.out.println(event);
}

@Override
public void connectionRemoved(final ConnectionRemovedEvent event) {
System.out.println(event);
}
}


The following example creates an instance of ``MongoClientSettings``
configured with an instance of ``TestConnectionPoolListener``:

.. code-block:: java

List<ServerAddress> seedList = ...
MongoClientSettings settings = MongoClientSettings.builder()
.applyToConnectionPoolSettings(builder ->
builder.addConnectionPoolListener(new TestConnectionPoolListener()))
.build();
MongoClient client = MongoClients.create(settings);

A ``MongoClient`` configured with these options prints a message to
``System.out`` for each connection pool-related event for each MongoDB
server to which the MongoClient is connected.
Overview
--------

In this guide, you can learn how to set up and configure **monitoring** in the
{+driver-short+}.

Monitoring is the process of gathering information about the activities a running
program performs for use in an application or an application performance
management library.

Monitoring the MongoDB Java driver lets you understand the
driver's resource usage and performance, and can help you make informed
decisions when designing and debugging your application.

In this guide you will learn how to perform the following tasks:

- :ref:`Monitor different types of events in the MongoDB Java Driver <java-rs-monitoring-events>`
- :ref:`Monitor connection pool events with Java Management Extensions (JMX) and JConsole <java-rs-monitoring-jmx>`

.. TODO: This guide shows how to use information about the activity of the driver in code.
.. To learn how to record events in the driver, see :doc:`</logging>`.

.. _java-rs-monitoring-events:

Monitor Events
--------------

To monitor an **event**, you must register a **listener** on your ``MongoClient``
instance.

An event is any action that happens in a running program. The driver includes functionality
for listening to a subset of the events that occur when the driver is running.

A listener is a class that performs some action when certain events occur.
A listener's API defines the events it can respond to.

Each method of a listener class represents a response to a certain event. Each
method receives one argument: an object representing the event the method
responds to.

The MongoDB Java driver organizes the events it defines into three categories:

- Command events
- Server discovery and monitoring events
- Connection pool events

The following sections show how to monitor each event category.

For a full list of the events you can monitor,
`see the event package of the MongoDB Java Driver <{+api+}/mongodb-driver-core/com/mongodb/event/package-summary.html>`__.

.. _java-rs-command-events:

Command Events
~~~~~~~~~~~~~~

A command event is an event related to a MongoDB database command. Some
examples of database commands that produce command events are ``find``,
``insert``, ``delete``, and ``count``.

To monitor command events, create a class that implements the
``CommandListener`` interface and register an instance of that class with your
``MongoClient`` instance.

For more information about MongoDB database commands, see :manual:`Database Commands </reference/command/>`
in the {+mdb-server+} manual.

.. note:: Internal Commands

The driver does not publish events for commands it calls internally. This
includes database commands the driver uses to monitor your cluster and
commands related to connection establishment (such as the initial ``hello``
command).

.. important:: Redacted Output

As a security measure, the driver redacts the contents of some command events. This
protects the sensitive information contained in these command events. For a
full list of redacted command events, see the
:spec:`MongoDB command logging and monitoring specification </command-logging-and-monitoring/command-logging-and-monitoring.rst#security>`.

Example
^^^^^^^

The following example shows how to make a counter for database commands. The counter
keeps track of the number of times the driver successfully executes each database
command, and prints this information every time a database command finishes.

To implement the counter, perform the following steps:

#. Make a class with counter functionality that implements the ``CommandListener`` interface.
#. Add an instance of the new class that implements ``CommandListener`` to a ``MongoClientSettings`` object.
#. Configure a ``MongoClient`` instance with the ``MongoClientSettings`` object.

The following code defines the ``CommandCounter`` class which implements the
``CommandListener`` interface:

.. literalinclude:: /includes/monitoring/Monitoring.java
:language: java
:start-after: start-command-listener
:end-before: end-command-listener
:copyable: true
:dedent:

The following code adds an instance of the ``CommandCounter`` class to a
``MongoClientSettings`` object, and configures a ``MongoClient`` instance with the
``MongoClientSettings`` object. The code then runs some database commands to test the
counter.

.. _java-rs-listener-mongo-client-settings-example:

.. io-code-block::
:copyable: true

.. input:: /includes/monitoring/Monitoring.java
:language: java
:start-after: start-monitor-command-example
:end-before: end-monitor-command-example
:dedent:

.. output::
:visible: false

{find=1}
{find=2}
{find=2, endSessions=1}


Server Discovery and Monitoring Events
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A server discovery and monitoring (SDAM) event is an event related to a change
in the state of the MongoDB instance or cluster you have connected the driver to.

The driver defines nine SDAM events. The driver divides these nine events
between three separate listener interfaces which each listen for three of the
nine events. Here are the three interfaces and the events they listen for:

- ``ClusterListener``: :spec:`topology </server-discovery-and-monitoring/server-discovery-and-monitoring.md#topology>`-related
events
- ``ServerListener``: events related to ``mongod`` or ``mongos`` processes
- ``ServerMonitorListener``: heartbeat-related events

To monitor a type of SDAM event, write a class that
implements one of the three preceding interfaces and register an instance of that
class with your ``MongoClient`` instance.

For a detailed description of each SDAM event in the driver, see the
:spec:`MongoDB SDAM Logging and Monitoring Specification </server-discovery-and-monitoring/server-discovery-and-monitoring-logging-and-monitoring.md#events-api>`.

.. note:: Load Balanced Mode

The driver doesn't emit heartbeat related events when in load balanced mode. For more details about SDAM events with load balancing, see :spec:`MongoDB Load Balancer Support Specification </load-balancers/load-balancers.md#server-discovery-logging-and-monitoring>`.

Example
^^^^^^^

The following example shows how to make a listener class that prints a message that lets
you know if the driver can write to your MongoDB instance.

The following code defines the ``IsWritable`` class which implements the
``ClusterListener`` interface:

.. literalinclude:: /includes/monitoring/Monitoring.java
:language: java
:start-after: start-cluster-listener
:end-before: end-cluster-listener
:copyable: true
:dedent:

The following code adds an instance of the ``IsWritable`` class to a
``MongoClient`` object. The code then runs a find operation to test the
``IsWritable`` class.

.. io-code-block::
:copyable: true

.. input:: /includes/monitoring/Monitoring.java
:language: java
:start-after: start-monitor-cluster-example
:end-before: end-monitor-cluster-example
:dedent:

.. output::
:visible: false

Able to write to server

Connection Pool Events
~~~~~~~~~~~~~~~~~~~~~~

A connection pool event is an event related to a **connection pool** held by the driver.
A connection pool is a set of open TCP connections your driver maintains with
a MongoDB deployment. Connection pools help reduce the number of network handshakes
your application needs to perform with a MongoDB deployment, and can help your
application run faster.

To monitor connection pool events, write a class that implements the
``ConnectionPoolListener`` interface and register an instance of that class with your
``MongoClient`` instance.

Example
^^^^^^^

The following example shows how to make a listener class that prints a message each time
you check out a connection from your connection pool.

The following code defines the ``ConnectionPoolLibrarian`` class which implements the
``ConnectionPoolListener`` interface:

.. literalinclude:: /includes/monitoring/Monitoring.java
:language: java
:start-after: start-connection-pool-listener
:end-before: end-connection-pool-listener
:copyable: true
:dedent:

The following code adds an instance of the ``ConnectionPoolLibrarian`` class to a
``MongoClient`` object. The code then runs a database command to test the
librarian.

.. io-code-block::
:copyable: true

.. input:: /includes/monitoring/Monitoring.java
:language: java
:start-after: start-monitor-cluster-example
:end-before: end-monitor-cluster-example
:dedent:

.. output::
:visible: false

Let me get you the connection with id 21...

.. _java-rs-monitoring-jmx:

Monitor Connection Pool Events with JMX
---------------------------------------

You can monitor connection pool events using **Java Management Extensions (JMX)**.
JMX provides tools to monitor applications and devices.

For more information about JMX, see
`the official Oracle JMX documentation <https://docs.oracle.com/javase/tutorial/jmx/index.html>`__.

JMX Support
~~~~~~~~~~~

To enable JMX connection pool monitoring, add an instance of the
``JMXConnectionPoolListener`` class to your ``MongoClient`` object.

The ``JMXConnectionPoolListener`` class performs the following actions:

#. Creates MXBean instances for each ``mongod`` or ``mongos`` process the driver
maintains a connection pool with
#. Registers these MXBean instances with the platform MBean server

MXBeans registered on the platform MBean server have the following properties:

.. list-table::
:header-rows: 1
:widths: 10 20

* - Property
- Description

* - ``clusterId``
- A client-generated unique identifier. This identifier ensures that
each MXBean the driver makes has a unique name when an application has
multiple ``MongoClient`` instances connected to the same MongoDB deployment.

* - ``host``
- The hostname of the machine running the ``mongod`` or ``mongos`` process.

* - ``port``
- The port on which the ``mongod`` or ``mongos`` process is listening.

* - ``minSize``
- The minimum size of the connection pool, including idle and in-use connections.

* - ``maxSize``
- The maximum size of the connection pool, including idle and in-use connections.

* - ``size``
- The current size of the connection pool, including idle and in-use connections.

* - ``checkedOutCount``
- The current count of connections that are in use.


All MXBean instances created by the driver are under the domain
``"org.mongodb.driver"``.

For more information about the topics discussed in this subsection, see the
following resources from Oracle:

- `Platform MBean Server Reference Documentation <https://docs.oracle.com/en/java/javase/16/management/overview-java-se-monitoring-and-management.html#GUID-F7B9AB8A-F5A8-472A-AEC6-93B5B7FBE7CE>`__
- `MXBean Documentation <https://docs.oracle.com/javase/tutorial/jmx/mbeans/mxbeans.html>`__
- `MBean Documentation <https://docs.oracle.com/javase/tutorial/jmx/mbeans/standard.html>`__

JMX and JConsole Example
~~~~~~~~~~~~~~~~~~~~~~~~

The following example shows how you can monitor the driver's connection pools using JMX
and **JConsole**. JConsole is a JMX-compliant GUI monitoring tool that comes with
the Java Platform.

.. tip:: Consult the Official JMX and JConsole Documentation

The descriptions of JMX and JConsole in this example are illustrative
rather than a source of truth. For guaranteed up-to-date information, consult
the following official Oracle resources:

- `JConsole documentation <https://www.oracle.com/technical-resources/articles/java/jconsole.html>`__
- `JMX documentation <https://docs.oracle.com/javase/tutorial/jmx/index.html>`__

The following code snippet adds a ``JMXConnectionPoolListener`` to a
``MongoClient`` instance. The code then pauses execution so you can
navigate to JConsole and inspect your connection pools.

.. io-code-block::
:copyable: true

.. input:: /includes/monitoring/JMXMonitoring.java
:language: java
:start-after: start-jmx-example
:end-before: end-jmx-example
:dedent:

.. output::
:visible: false

Navigate to JConsole to see your connection pools...

Once you have started your server, open JConsole by running the following command in your
terminal:

.. code-block:: shell

jconsole

Once JConsole is open, perform the following actions in the GUI:

#. Select the Java process running the preceding example code.
#. Click :guilabel:`Insecure Connection` in the warning dialog box.
#. Click on the :guilabel:`MBeans` tab.
#. Inspect your connection pool events under the ``"org.mongodb.driver"`` domain.

When you no longer want to inspect your connection pools in JConsole, perform the
following tasks:

#. Exit JConsole by closing the JConsole window.
#. Stop the Java program running the preceding code snippet.

For more information about JMX and JConsole, see the following resources from
Oracle:

- `JConsole Documentation <https://www.oracle.com/technical-resources/articles/java/jconsole.html>`__
- `Monitoring and Management Guide <https://docs.oracle.com/en/java/javase/16/management/monitoring-and-management-using-jmx-technology.html>`__

For more information about the ``JMXConnectionPoolListener`` class, see
the API Documentation for
`JMXConnectionPoolListener <{+api+}/mongodb-driver-core/com/mongodb/management/JMXConnectionPoolListener.html>`__.

Include the Driver in Your Distributed Tracing System
-----------------------------------------------------

If you use a **distributed tracing system**, you can include event data from the
driver. A distributed tracing system is an application that
tracks requests as they propagate throughout different services in a
service-oriented architecture.

If you use the driver in a `Spring Cloud <https://spring.io/projects/spring-cloud>`__
application, use
`Spring Cloud Sleuth <https://spring.io/projects/spring-cloud-sleuth>`__ to
include MongoDB event data in the
`Zipkin <https://zipkin.io/>`__ distributed tracing system.

If you do not use Spring Cloud or must include driver event data in a distributed
tracing system other than Zipkin, you must write a command event listener that
manages `spans <https://docs.spring.io/spring-cloud-sleuth/docs/current-SNAPSHOT/reference/html/getting-started.html#getting-started-terminology>`__
for your desired distributed tracing system. To see an implementation of such a
listener, see the
:github:`TraceMongoCommandListener
<spring-cloud/spring-cloud-sleuth/blob/eb01d5952b2e736970efbe3fc7e9784d119eeae9/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/mongodb/TraceMongoCommandListener.java>`
class in the Spring Cloud Sleuth source code.

To learn more about Spring Cloud Sleuth, see
`Getting Started <https://docs.spring.io/spring-cloud-sleuth/docs/current-SNAPSHOT/reference/html/getting-started.html>`__
in the Spring Cloud Sleuth documentation.

To view a detailed description of a distributed tracing system, see
`Dapper <https://research.google/pubs/pub36356/>`__ from Google Research.

API Documentation
-----------------

For more information about the classes and methods mentioned in this document, see
the following API Documentation:

- `MongoClients <{+api+}/mongodb-driver-reactivestreams/com/mongodb/reactivestreams/client/MongoClients.html>`__
- `MongoClientSettings <{+api+}/mongodb-driver-core/com/mongodb/MongoClientSettings.html>`__
- `CommandListener <{+api+}/mongodb-driver-core/com/mongodb/event/CommandListener.html>`__
- `CommandStartedEvent <{+api+}/mongodb-driver-core/com/mongodb/event/CommandStartedEvent.html>`__
- `CommandSucceededEvent <{+api+}/mongodb-driver-core/com/mongodb/event/CommandSucceededEvent.html>`__
- `CommandFailedEvent <{+api+}/mongodb-driver-core/com/mongodb/event/CommandFailedEvent.html>`__
- `ClusterListener <{+api+}/mongodb-driver-core/com/mongodb/event/ClusterListener.html>`__
- `ClusterDescriptionChangedEvent <{+api+}/mongodb-driver-core/com/mongodb/event/ClusterDescriptionChangedEvent.html>`__
- `ConnectionPoolListener <{+api+}/mongodb-driver-core/com/mongodb/event/ConnectionPoolListener.html>`__
- `ConnectionCheckedOutEvent <{+api+}/mongodb-driver-core/com/mongodb/event/ConnectionCheckedOutEvent.html>`__
- `ConnectionCheckOutFailedEvent <{+api+}/mongodb-driver-core/com/mongodb/event/ConnectionCheckOutFailedEvent.html>`__