# Application Performance Monitoring (APM)

The MongoDB C Driver allows you to monitor all the MongoDB operations the driver executes. This event-notification system conforms to two MongoDB driver specs:

- Command Monitoring: events related to all application operations.

- SDAM Monitoring: events related to the driver’s Server Discovery And Monitoring logic.

To receive notifications, create a `mongoc_apm_callbacks_t` with mongoc_apm_callbacks_new(), set callbacks on it, then pass it to mongoc_client_set_apm_callbacks() or mongoc_client_pool_set_apm_callbacks().

## Command-Monitoring Example

example-command-monitoring.c

    /* gcc example-command-monitoring.c -o example-command-monitoring \
     *     $(pkg-config --cflags --libs libmongoc-1.0) */

    /* ./example-command-monitoring [CONNECTION_STRING] */

    #include <mongoc/mongoc.h>
    #include <stdio.h>

    typedef struct {
       int started;
       int succeeded;
       int failed;
    } stats_t;

    void
    command_started (const mongoc_apm_command_started_t *event)
    {
       char *s;

       s = bson_as_relaxed_extended_json (
          mongoc_apm_command_started_get_command (event), NULL);
       printf ("Command %s started on %s:\n%s\n\n",
               mongoc_apm_command_started_get_command_name (event),
               mongoc_apm_command_started_get_host (event)->host,
               s);

       ((stats_t *) mongoc_apm_command_started_get_context (event))->started++;

       bson_free (s);
    }

    void
    command_succeeded (const mongoc_apm_command_succeeded_t *event)
    {
       char *s;

       s = bson_as_relaxed_extended_json (
          mongoc_apm_command_succeeded_get_reply (event), NULL);
       printf ("Command %s succeeded:\n%s\n\n",
               mongoc_apm_command_succeeded_get_command_name (event),
               s);

       ((stats_t *) mongoc_apm_command_succeeded_get_context (event))->succeeded++;

       bson_free (s);
    }

    void
    command_failed (const mongoc_apm_command_failed_t *event)
    {
       bson_error_t error;

       mongoc_apm_command_failed_get_error (event, &error);
       printf ("Command %s failed:\n\"%s\"\n\n",
               mongoc_apm_command_failed_get_command_name (event),
               error.message);

       ((stats_t *) mongoc_apm_command_failed_get_context (event))->failed++;
    }

    int
    main (int argc, char *argv[])
    {
       mongoc_client_t *client;
       mongoc_apm_callbacks_t *callbacks;
       stats_t stats = {0};
       mongoc_collection_t *collection;
       bson_error_t error;
       const char *uri_string =
          "mongodb://127.0.0.1/?appname=cmd-monitoring-example";
       mongoc_uri_t *uri;
       const char *collection_name = "test";
       bson_t *docs[2];

       mongoc_init ();

       if (argc > 1) {
          uri_string = argv[1];
       }

       uri = mongoc_uri_new_with_error (uri_string, &error);
       if (!uri) {
          fprintf (stderr,
                   "failed to parse URI: %s\n"
                   "error message:       %s\n",
                   uri_string,
                   error.message);
          return EXIT_FAILURE;
       }

       client = mongoc_client_new_from_uri (uri);
       if (!client) {
          return EXIT_FAILURE;
       }

       mongoc_client_set_error_api (client, 2);
       callbacks = mongoc_apm_callbacks_new ();
       mongoc_apm_set_command_started_cb (callbacks, command_started);
       mongoc_apm_set_command_succeeded_cb (callbacks, command_succeeded);
       mongoc_apm_set_command_failed_cb (callbacks, command_failed);
       mongoc_client_set_apm_callbacks (
          client, callbacks, (void *) &stats /* context pointer */);

       collection = mongoc_client_get_collection (client, "test", collection_name);
       mongoc_collection_drop (collection, NULL);

       docs[0] = BCON_NEW ("_id", BCON_INT32 (0));
       docs[1] = BCON_NEW ("_id", BCON_INT32 (1));
       mongoc_collection_insert_many (
          collection, (const bson_t **) docs, 2, NULL, NULL, NULL);

       /* duplicate key error on the second insert */
       mongoc_collection_insert_one (collection, docs[0], NULL, NULL, NULL);

       mongoc_collection_destroy (collection);
       mongoc_apm_callbacks_destroy (callbacks);
       mongoc_uri_destroy (uri);
       mongoc_client_destroy (client);

       printf ("started: %d\nsucceeded: %d\nfailed: %d\n",
               stats.started,
               stats.succeeded,
               stats.failed);

       bson_destroy (docs[0]);
       bson_destroy (docs[1]);

       mongoc_cleanup ();

       return EXIT_SUCCESS;
    }

This example program prints:

    Command drop started on 127.0.0.1:
    { "drop" : "test" }

    Command drop succeeded:
    { "ns" : "test.test", "nIndexesWas" : 1, "ok" : 1.0 }

    Command insert started on 127.0.0.1:
    {
      "insert" : "test",
      "ordered" : true,
      "documents" : [
        { "_id" : 0 }, { "_id" : 1 }
      ]
    }

    Command insert succeeded:
    { "n" : 2, "ok" : 1.0 }

    Command insert started on 127.0.0.1:
    {
      "insert" : "test",
      "ordered" : true,
      "documents" : [
        { "_id" : 0 }
      ]
    }

    Command insert succeeded:
    {
      "n" : 0,
      "writeErrors" : [
        { "index" : 0, "code" : 11000, "errmsg" : "duplicate key" }
      ],
      "ok" : 1.0
    }

    started: 3
    succeeded: 3
    failed: 0

The output has been edited and formatted for clarity. Depending on your server configuration, messages may include metadata like database name, logical session ids, or cluster times that are not shown here.

The final “insert” command is considered successful, despite the writeError, because the server replied to the overall command with `"ok": 1`.

## SDAM Monitoring Example

example-sdam-monitoring.c

    /* gcc example-sdam-monitoring.c -o example-sdam-monitoring \
     *     $(pkg-config --cflags --libs libmongoc-1.0) */

    /* ./example-sdam-monitoring [CONNECTION_STRING] */

    #include <mongoc/mongoc.h>
    #include <stdio.h>

    typedef struct {
       int server_changed_events;
       int server_opening_events;
       int server_closed_events;
       int topology_changed_events;
       int topology_opening_events;
       int topology_closed_events;
       int heartbeat_started_events;
       int heartbeat_succeeded_events;
       int heartbeat_failed_events;
    } stats_t;

    static void
    server_changed (const mongoc_apm_server_changed_t *event)
    {
       stats_t *context;
       const mongoc_server_description_t *prev_sd, *new_sd;

       context = (stats_t *) mongoc_apm_server_changed_get_context (event);
       context->server_changed_events++;

       prev_sd = mongoc_apm_server_changed_get_previous_description (event);
       new_sd = mongoc_apm_server_changed_get_new_description (event);

       printf ("server changed: %s %s -> %s\n",
               mongoc_apm_server_changed_get_host (event)->host_and_port,
               mongoc_server_description_type (prev_sd),
               mongoc_server_description_type (new_sd));
    }

    static void
    server_opening (const mongoc_apm_server_opening_t *event)
    {
       stats_t *context;

       context = (stats_t *) mongoc_apm_server_opening_get_context (event);
       context->server_opening_events++;

       printf ("server opening: %s\n",
               mongoc_apm_server_opening_get_host (event)->host_and_port);
    }

    static void
    server_closed (const mongoc_apm_server_closed_t *event)
    {
       stats_t *context;

       context = (stats_t *) mongoc_apm_server_closed_get_context (event);
       context->server_closed_events++;

       printf ("server closed: %s\n",
               mongoc_apm_server_closed_get_host (event)->host_and_port);
    }

    static void
    topology_changed (const mongoc_apm_topology_changed_t *event)
    {
       stats_t *context;
       const mongoc_topology_description_t *prev_td;
       const mongoc_topology_description_t *new_td;
       mongoc_server_description_t **prev_sds;
       size_t n_prev_sds;
       mongoc_server_description_t **new_sds;
       size_t n_new_sds;
       size_t i;
       mongoc_read_prefs_t *prefs;

       context = (stats_t *) mongoc_apm_topology_changed_get_context (event);
       context->topology_changed_events++;

       prev_td = mongoc_apm_topology_changed_get_previous_description (event);
       prev_sds = mongoc_topology_description_get_servers (prev_td, &n_prev_sds);
       new_td = mongoc_apm_topology_changed_get_new_description (event);
       new_sds = mongoc_topology_description_get_servers (new_td, &n_new_sds);

       printf ("topology changed: %s -> %s\n",
               mongoc_topology_description_type (prev_td),
               mongoc_topology_description_type (new_td));

       if (n_prev_sds) {
          printf ("  previous servers:\n");
          for (i = 0; i < n_prev_sds; i++) {
             printf ("      %s %s\n",
                     mongoc_server_description_type (prev_sds[i]),
                     mongoc_server_description_host (prev_sds[i])->host_and_port);
          }
       }

       if (n_new_sds) {
          printf ("  new servers:\n");
          for (i = 0; i < n_new_sds; i++) {
             printf ("      %s %s\n",
                     mongoc_server_description_type (new_sds[i]),
                     mongoc_server_description_host (new_sds[i])->host_and_port);
          }
       }

       prefs = mongoc_read_prefs_new (MONGOC_READ_SECONDARY);

       /* it is safe, and unfortunately necessary, to cast away const here */
       if (mongoc_topology_description_has_readable_server (
              (mongoc_topology_description_t *) new_td, prefs)) {
          printf ("  secondary AVAILABLE\n");
       } else {
          printf ("  secondary UNAVAILABLE\n");
       }

       if (mongoc_topology_description_has_writable_server (
              (mongoc_topology_description_t *) new_td)) {
          printf ("  primary AVAILABLE\n");
       } else {
          printf ("  primary UNAVAILABLE\n");
       }

       mongoc_read_prefs_destroy (prefs);
       mongoc_server_descriptions_destroy_all (prev_sds, n_prev_sds);
       mongoc_server_descriptions_destroy_all (new_sds, n_new_sds);
    }

    static void
    topology_opening (const mongoc_apm_topology_opening_t *event)
    {
       stats_t *context;

       context = (stats_t *) mongoc_apm_topology_opening_get_context (event);
       context->topology_opening_events++;

       printf ("topology opening\n");
    }

    static void
    topology_closed (const mongoc_apm_topology_closed_t *event)
    {
       stats_t *context;

       context = (stats_t *) mongoc_apm_topology_closed_get_context (event);
       context->topology_closed_events++;

       printf ("topology closed\n");
    }

    static void
    server_heartbeat_started (const mongoc_apm_server_heartbeat_started_t *event)
    {
       stats_t *context;

       context =
          (stats_t *) mongoc_apm_server_heartbeat_started_get_context (event);
       context->heartbeat_started_events++;

       printf ("%s heartbeat started\n",
               mongoc_apm_server_heartbeat_started_get_host (event)->host_and_port);
    }

    static void
    server_heartbeat_succeeded (
       const mongoc_apm_server_heartbeat_succeeded_t *event)
    {
       stats_t *context;
       char *reply;

       context =
          (stats_t *) mongoc_apm_server_heartbeat_succeeded_get_context (event);
       context->heartbeat_succeeded_events++;

       reply = bson_as_canonical_extended_json (
          mongoc_apm_server_heartbeat_succeeded_get_reply (event), NULL);

       printf (
          "%s heartbeat succeeded: %s\n",
          mongoc_apm_server_heartbeat_succeeded_get_host (event)->host_and_port,
          reply);

       bson_free (reply);
    }

    static void
    server_heartbeat_failed (const mongoc_apm_server_heartbeat_failed_t *event)
    {
       stats_t *context;
       bson_error_t error;

       context = (stats_t *) mongoc_apm_server_heartbeat_failed_get_context (event);
       context->heartbeat_failed_events++;
       mongoc_apm_server_heartbeat_failed_get_error (event, &error);

       printf ("%s heartbeat failed: %s\n",
               mongoc_apm_server_heartbeat_failed_get_host (event)->host_and_port,
               error.message);
    }

    int
    main (int argc, char *argv[])
    {
       mongoc_client_t *client;
       mongoc_apm_callbacks_t *cbs;
       stats_t stats = {0};
       const char *uri_string =
          "mongodb://127.0.0.1/?appname=sdam-monitoring-example";
       mongoc_uri_t *uri;
       bson_t cmd = BSON_INITIALIZER;
       bson_t reply;
       bson_error_t error;

       mongoc_init ();

       if (argc > 1) {
          uri_string = argv[1];
       }

       uri = mongoc_uri_new_with_error (uri_string, &error);
       if (!uri) {
          fprintf (stderr,
                   "failed to parse URI: %s\n"
                   "error message:       %s\n",
                   uri_string,
                   error.message);
          return EXIT_FAILURE;
       }

       client = mongoc_client_new_from_uri (uri);
       if (!client) {
          return EXIT_FAILURE;
       }

       mongoc_client_set_error_api (client, 2);
       cbs = mongoc_apm_callbacks_new ();
       mongoc_apm_set_server_changed_cb (cbs, server_changed);
       mongoc_apm_set_server_opening_cb (cbs, server_opening);
       mongoc_apm_set_server_closed_cb (cbs, server_closed);
       mongoc_apm_set_topology_changed_cb (cbs, topology_changed);
       mongoc_apm_set_topology_opening_cb (cbs, topology_opening);
       mongoc_apm_set_topology_closed_cb (cbs, topology_closed);
       mongoc_apm_set_server_heartbeat_started_cb (cbs, server_heartbeat_started);
       mongoc_apm_set_server_heartbeat_succeeded_cb (cbs,
                                                     server_heartbeat_succeeded);
       mongoc_apm_set_server_heartbeat_failed_cb (cbs, server_heartbeat_failed);
       mongoc_client_set_apm_callbacks (
          client, cbs, (void *) &stats /* context pointer */);

       /* the driver connects on demand to perform first operation */
       BSON_APPEND_INT32 (&cmd, "buildinfo", 1);
       mongoc_client_command_simple (client, "admin", &cmd, NULL, &reply, &error);
       mongoc_uri_destroy (uri);
       mongoc_client_destroy (client);

       printf ("Events:\n"
               "   server changed: %d\n"
               "   server opening: %d\n"
               "   server closed: %d\n"
               "   topology changed: %d\n"
               "   topology opening: %d\n"
               "   topology closed: %d\n"
               "   heartbeat started: %d\n"
               "   heartbeat succeeded: %d\n"
               "   heartbeat failed: %d\n",
               stats.server_changed_events,
               stats.server_opening_events,
               stats.server_closed_events,
               stats.topology_changed_events,
               stats.topology_opening_events,
               stats.topology_closed_events,
               stats.heartbeat_started_events,
               stats.heartbeat_succeeded_events,
               stats.heartbeat_failed_events);

       bson_destroy (&cmd);
       bson_destroy (&reply);
       mongoc_apm_callbacks_destroy (cbs);

       mongoc_cleanup ();

       return EXIT_SUCCESS;
    }

Start a 3-node replica set on localhost with set name “rs” and start the program:

    ./example-sdam-monitoring "mongodb://localhost:27017,localhost:27018/?replicaSet=rs"

This example program prints something like:

    topology opening
    topology changed: Unknown -> ReplicaSetNoPrimary
      secondary UNAVAILABLE
      primary UNAVAILABLE
    server opening: localhost:27017
    server opening: localhost:27018
    localhost:27017 heartbeat started
    localhost:27018 heartbeat started
    localhost:27017 heartbeat succeeded: { ... reply ... }
    server changed: localhost:27017 Unknown -> RSPrimary
    server opening: localhost:27019
    topology changed: ReplicaSetNoPrimary -> ReplicaSetWithPrimary
      new servers:
          RSPrimary localhost:27017
      secondary UNAVAILABLE
      primary AVAILABLE
    localhost:27019 heartbeat started
    localhost:27018 heartbeat succeeded: { ... reply ... }
    server changed: localhost:27018 Unknown -> RSSecondary
    topology changed: ReplicaSetWithPrimary -> ReplicaSetWithPrimary
      previous servers:
          RSPrimary localhost:27017
      new servers:
          RSPrimary localhost:27017
          RSSecondary localhost:27018
      secondary AVAILABLE
      primary AVAILABLE
    localhost:27019 heartbeat succeeded: { ... reply ... }
    server changed: localhost:27019 Unknown -> RSSecondary
    topology changed: ReplicaSetWithPrimary -> ReplicaSetWithPrimary
      previous servers:
          RSPrimary localhost:27017
          RSSecondary localhost:27018
      new servers:
          RSPrimary localhost:27017
          RSSecondary localhost:27018
          RSSecondary localhost:27019
      secondary AVAILABLE
      primary AVAILABLE
    topology closed

    Events:
       server changed: 3
       server opening: 3
       server closed: 0
       topology changed: 4
       topology opening: 1
       topology closed: 1
       heartbeat started: 3
       heartbeat succeeded: 3
       heartbeat failed: 0

The driver connects to the mongods on ports 27017 and 27018, which were specified in the URI, and determines which is primary. It also discovers the third member, “localhost:27019”, and adds it to the topology.

## Functions

- mongoc_apm_callbacks_t
- mongoc_apm_command_failed_t
- mongoc_apm_command_started_t
- mongoc_apm_command_succeeded_t
- mongoc_apm_server_changed_t
- mongoc_apm_server_closed_t
- mongoc_apm_server_heartbeat_failed_t
- mongoc_apm_server_heartbeat_started_t
- mongoc_apm_server_heartbeat_succeeded_t
- mongoc_apm_server_opening_t
- mongoc_apm_topology_changed_t
- mongoc_apm_topology_closed_t
- mongoc_apm_topology_opening_t