Add the AppDynamics Header File to the Application

After downloading the SDK, you are ready to add AppDynamics instrumentation to your C/C++ application. The first step is to include the AppDynamics header file at the top of your main function file:

#include <path_to_SDK>/sdk_lib/appdynamics.h
CODE

Initialize the Controller Configuration

Controller information settings permit the agent to connect to the Controller. Some settings are required for all applications, while others are needed only for certain types of application environments. For example, if the agent needs to connect to the Controller via an internal proxy in your network, you need to set up the connection settings for the proxy. See C/C++ SDK Reference for a list of the settings and information about which ones are required.

In your application, assign values to the required settings.

For example, to set the Controller connection information:

const char APP_NAME[] = "SampleC";
const char TIER_NAME[] = "SampleCTier1";
const char NODE_NAME[] = "SampleCNode1";
const char CONTROLLER_HOST[] = "controller.somehost.com";
const int CONTROLLER_PORT = 8080;
const char CONTROLLER_ACCOUNT[] = "customer1";
const char CONTROLLER_ACCESS_KEY[] = "MyAccessKey";
const int CONTROLLER_USE_SSL = 0; 
CPP

To create the default application context, declare an appd_config struct with the Controller settings.

For example: 

struct appd_config* cfg = appd_config_init(); // appd_config_init() resets the configuration object and pass back an handle/pointer
appd_config_set_app_name(cfg, APP_NAME);
appd_config_set_tier_name(cfg, TIER_NAME);
appd_config_set_node_name(cfg, NODE_NAME);
appd_config_set_controller_host(cfg, CONTROLLER_HOST);
appd_config_set_controller_port(cfg, CONTROLLER_PORT);
appd_config_set_controller_account(cfg, CONTROLLER_ACCOUNT);
appd_config_set_controller_access_key(cfg, CONTROLLER_ACCESS_KEY);
appd_config_set_controller_use_ssl(cfg, CONTROLLER_USE_SSL);
CPP

Initialize the SDK

In your main function, call these initialization functions:

  1. Initialize the config structure as shown in the previous section.
  2. Initialize the SDK by passing the configuration structure to appd_sdk_init().

If appd_sdk_init() returns zero, the SDK is initialized successfully. Non-zero indicates failure because the SDK could not reach the Controller.

The following example illustrates how to initialize the SDK:

int initRC = appd_sdk_init(cfg);

if (initRC) {
      std::cerr <<  "Error: sdk init: " << initRC << std::endl;
      return -1;
} 
CPP

All SDK calls including appd_sdk_init() and fork() should only occur in the forked process, never in the parent process. Each child call should behave independently and cannot share handles between the process and other SDK-instrumented child processes.

Create Alternate Application Contexts

The SDK presumes a default application context that has no name associated with it. 

To create the default application context, use the appd_config_init() command which returns a pointer to the default configuration structure. Then, use setter commands to modify the fields within the structure. Once all relevant fields are set, use the appd_sdk_init() call to initialize the SDK with the default application context.

All other application contexts are created with an associated name and a collection of separate setter methods. 

To create an alternate application context, pass the appd_context_config_init() command the name of your alternate application context. This will return a pointer to the alternate application context configuration structure. Then, use the _context_ setter commands to modify the fields within the structure. Once all relevant fields are set, use the appd_sdk_add_app_context() call to initialize the SDK, passing it the alternate application context structure pointer.

// create the alternate application context
appd_context_config* cfg2 = appd_context_config_init("My_Context");
...

// add additional setter commands to initialize the alternate application context
appd_context_config_set_controller_account(cfg2, ac.second.ac_account_get().c_str());
appd_context_config_set_controller_access_key(cfg2, ac.second.ac_access_key_get().c_str());
appd_context_config_set_controller_host(cfg2, ac.second.ac_host_get().c_str());
appd_context_config_set_controller_port(cfg, ac.second.ac_port_get());
appd_context_config_set_controller_use_ssl(cfg2, ac.second.ac_use_ssl_get() ? 1 : 0);
appd_context_config_set_app_name(cfg2, ac.second.ac_app_get().c_str());
appd_context_config_set_tier_name(cfg2, ac.second.ac_tier_get().c_str());
appd_context_config_set_node_name(cfg2, ac.second.ac_node_get().c_str());

// initialize the alternate application context
appd_sdk_add_app_context(cfg2);
CODE

Create Business Transactions

Define a Business Transaction by enclosing the code that constitutes the request that you want to monitor between appd_bt_begin() and appd_bt_end() calls. appd_bt_begin() returns a handle to use in subsequent routines that affect that business transaction.

If you are creating a business transaction that correlates with an upstream business transaction, pass the correlation header of the upstream transaction so the new transaction that you are creating can correlate with it. See appd_exitcall_get_correlation_header() in C/C++ SDK Reference. If the transaction does not need to correlate with another transaction, pass NULL for the correlation header parameter.

You can optionally store the business transaction handle in the global handle registry with a guid for easy retrieval later using appd_bt_store(). Retrieve the handle from the global handle registry using appd_bt_get().

The following example demonstrates setting a business transaction:

// start the "Checkout" transaction
appd_bt_handle btHandle = appd_bt_begin("Checkout", NULL);

// Optionally store the handle in the global registry
appd_bt_store(btHandle, my_bt_guid);
... 

// Retrieve a stored handle from the global registry
appd_bt_handle myBtHandle = appd_bt_get(my_bt_guid);
... 

// end the transaction
appd_bt_end(btHandle);
CPP

Between starting and ending the transaction, you can perform operations such as adding errors to the business transaction, defining the transaction snapshot attributes, adding backends and exit calls, and so on. 

When the business transaction ends, via a call to appd_bt_end(), the agent stops reporting metrics for the business transaction.

Create Business Transactions With an Alternate Application Context

You can create a business transaction with an alternate application context using appd_bt_begin_with_app_context() and appd_bt_end() calls. In addition to the parameters passed to appd_bt_begin(), the appd_bt_begin_with_app_context() also takes an application context name string, as defined by the call to appd_context_config_init().

appd_bt_begin_with_app_context() returns a handle to use in subsequent routines that affect that business transaction. 

The following example demonstrates setting a business transaction with an alternate application context:

// create the alternate application context
appd_context_config* cfg2 = appd_context_config_init("My_Context");
...


// add additional setter commands to initialize the alternate application context
appd_context_config_set_controller_account(cfg2, ac.second.ac_account_get().c_str());
appd_context_config_set_controller_access_key(cfg2, ac.second.ac_access_key_get().c_str());
appd_context_config_set_controller_host(cfg2, ac.second.ac_host_get().c_str());
appd_context_config_set_controller_port(cfg, ac.second.ac_port_get());
appd_context_config_set_controller_use_ssl(cfg2, ac.second.ac_use_ssl_get() ? 1 : 0);
appd_context_config_set_app_name(cfg2, ac.second.ac_app_get().c_str());
appd_context_config_set_tier_name(cfg2, ac.second.ac_tier_get().c_str());
appd_context_config_set_node_name(cfg2, ac.second.ac_node_get().c_str());

// initialize the alternate application context
appd_sdk_add_app_context(cfg2);


// start the "Checkout" transaction with the alternate application context
appd_bt_handle btHandle = appd_bt_begin_with_app_context("My_Context", "Checkout", NULL);
 
// end the transaction
appd_bt_end(btHandle);
CODE

When the business transaction ends, via a call to appd_bt_end(), the agent stops reporting metrics for the business transaction. Between starting and ending the transaction, you can perform operations such as adding errors to the business transaction, defining the transaction snapshot attributes, adding backends and exit calls, and so on. 

Add Business Transaction Errors

Use appd_bt_add_error() to report business transaction errors. If you set this function parameter, markBTAsError the transaction is reported as an error transaction when the error occurs. For the function {{appd_bt_add_error,}}, the Controller database truncates log messages that exceed 5,000 characters.

The SDK provides an enum to classify the error level as APPD_LEVEL_NOTICEAPPD_LEVEL_WARNING, or APPD_LEVEL_ERROR.

Manage Business Transaction Snapshots

When the agent is monitoring a business transaction, it automatically creates transaction snapshots, which describe instances of the business transaction at certain points in time. Transaction snapshots are extremely useful for troubleshooting poor performance because they contain a lot of detail.

You do not have to modify anything to create these snapshots, other than create the business transaction, but you can add calls to:

  • Find out if a snapshot is being taken
  • Provide additional data in a snapshot
  • Set the URL for a snapshot

Determine if the Agent is Taking a Snapshot Now

The agent does not constantly collect screenshot due to high cost. By default, it collects a snapshot every ten minutes, but this schedule is configurable. See Configure Snapshot Periodic Collection Frequency.

You can determine if a snapshot is happening using appd_bt_is_snapshotting(), which returns non-zero if the agent is collecting a snapshot. The main reason to do this is to avoid the wasted overhead for collecting user data for a snapshot or setting the snapshot URL if no snapshot is currently being collected.

Add Business Transaction User Data

You can optionally add data to transaction snapshots. For example, you might want to know which users are getting a lot of errors, from which regions users are experiencing slow response times or which methods are slow. In the Controller UI, the data appears in the USER DATA tab of the transaction snapshot.

If a snapshot is occurring, use appd_bt_add_user_data() passing a key and value for the data that you want the snapshot to collect. For the function {{appd_bt_add_user_data,}}, the Controller database truncates log messages that exceed 5,000 characters.

Add Snapshot URL

A snapshot URL allows Controller users to share a snapshot with others. You can set a URL for the current snapshot using appd_bt_set_url().

Set Snapshot Example

void setSnapshotAttributes(appd_bt_handle btHandle, int minute, int halfsec)
{
    // do this only if the agent is collecting a snapshot
    if (appd_bt_is_snapshotting(btHandle))
    {
        char nameBuf[30];
        char valueBuf[30];
        // add custom data to the snapshot
        snprintf(nameBuf, sizeof(nameBuf), "BT:%p\n", btHandle);
        snprintf(valueBuf, sizeof(valueBuf), "Minute:%d Second:%d\n", minute, halfsec/2);
        appd_bt_add_user_data(btHandle, nameBuf, valueBuf);

        static int snapCount = 0;
        int switchVal = snapCount % 4;

        // report errors, but only ERROR_LEVEL errors are marked as error transactions
        if (switchVal)
        {
            appd_error_level errorLevel;
            bool markBtAsError;
            switch (switchVal)
            {
            case 1:
                errorLevel = APPD_LEVEL_NOTICE;
                markBtAsError = false;
                snprintf(nameBuf, sizeof(nameBuf), "NOTICE BT:%p M:%d S:%d\n", btHandle, minute, halfsec/2);
                break;
            case 2:
                errorLevel = APPD_LEVEL_WARNING;
                markBtAsError = false;
                snprintf(nameBuf, sizeof(nameBuf), "WARNING BT:%p M:%d S:%d\n", btHandle, minute, halfsec/2);
                break;
            case 3:
                errorLevel = APPD_LEVEL_ERROR;
                markBtAsError = true;
                snprintf(nameBuf, sizeof(nameBuf), "ERROR BT:%p M:%d S:%d\n", btHandle, minute, halfsec/2);
                break;
            }
            appd_bt_add_error(btHandle, errorLevel, nameBuf, markBtAsError, markbtaserror);
        }
        snapCount++;
        // set the snapshot url
        snprintf(nameBuf, sizeof(nameBuf), "http://bt-%p.com", btHandle);
        appd_bt_set_url(btHandle, nameBuf);
    }
}
CPP

Create Backends

A backend is a database or a remote service such as a message queue, HTTP service or cache service that your application uses. A backend component is not itself monitored by the application agent, but the agent monitors calls to it from instrumented servers. You need to create backends in the instrumented environment so that the agent can discover them. This involves:

  • Declaring the backend
  • Setting its identifying properties
  • Optionally configuring how the backend is presented in the AppDynamics UI
  • Adding the backend to the instrumented application

Declare the Backend

You must declare a backend using appd_backend_declare() before the agent can detect it. After you declare a backend, you don't need to declare it again if the backend is used by other business transactions in a single SDK instance. A backend must be of one of the supported types listed under 'Exit Call Types' in C/C++ SDK Reference.

Identify the Backend

A backend also has identifying properties, which you set using appd_backend_set_identifying_property(). The properties vary depending on the type of the backend and the types of information that you want to display. The Controller displays identifying properties in backend dashboards. You must set at least one identifying property for the type of any backend that you plan to add. 

The following shows the backend properties in the Controller UI for an ActiveMQ:

Backend Properties

Resolve to a Tier

Add to Application

After you declare and configure the backend, add it to the application so the agent can detect it.

Backend Example

The following listing shows an example of setting up a database backend. 

// declare a backend, only once for this SDK instance
const char backendOne[] = "first backend";
appd_backend_declare(APPD_BACKEND_HTTP,  backendOne);

// set the host property
rc = appd_backend_set_identifying_property(backendOne, "HOST", "sqs-us-west-hostname");
if (rc) {
   std:cerr << "Error: appd_backend_set_identifying_property: " << rc << ".";
   return -1;
}

// do not resolve the backend to the tier
rc = appd_backend_prevent_agent_resolution(backendOne);
if (rc) {
   std:cerr << "Error: appd_backend_prevent_agent_resolution: " << rc << ".";
   return -1;
}

// add the backend
rc = appd_backend_add(backendOne);
if (rc)
{
   std:cerr << "Error: appd_backend_add: " << rc << ".";
   return -1;
} 
CPP

Manage Exit Calls

When an application makes a call to another component—a detected backend or another application server—the agent reports metrics on those calls.

Define an exit call by enclosing the code that constitutes the exit call between appd_exitcall_begin() and appd_exitcall_end() calls. appd_exitcall_begin() returns a handle to use in subsequent routines that affect that exit call. An exit call occurs in the context of a business transaction.

You can optionally store the exit call handle in the global handle registry with a guid for easy retrieval later with appd_exitcall_store(). Retrieve the handle from the global handle registry with appd_exitcall_get().

Pass the business transaction handle and the destination backend to appd_exitcall_end().

You can optionally add details to an exit call as any arbitrary string. The details are reported in the exit call details in the transaction snapshots in the Controller UI:

Exit Call Details

You can also add errors to an exit call using appd_exitcall_add_error(). Use the enum for the error levels. You can also add an error message.

Simple Exit Call Example

// start the exit call to backendOne
appd_exitcall_handle ecHandle = appd_exitcall_begin(btHandle, backendOne);

...

// optionally store the handle in the global registry
appd_exitcall_store(ecHandle, my_ec_guid);

...
// retrieve a stored handle from the global registry
appd_exitcall_handle myEcHandle = appd_exitcall_get(my_ec_guid);
 
// set the exit call details
rc = appd_exitcall_set_details(ecHandle, "backend ONE");
if (rc) {
   std:cerr << "Error: exitcall details1";
   return -1;
}

// add an error to the exit call
appd_exitcall_add_error(ecHandle, APPD_LEVEL_ERROR, "exitcall1 error!", true);

// end the exit call
appd_exitcall_end(ecHandle)
CPP

Correlate with Other Business Transactions

A correlation header contains the information that enables the agents to continue the flow of a business transaction across multiple tiers.

An SDK can correlate with other SDKs as well as other AppDynamics agents—such as Java, .NET or PHP—that perform automatic correlation for certain types of entry and exit points. 

Correlate with an Upstream Tier

When your instrumented process receives a continuing transaction from an upstream agent that supports automatic correlation:

  1. Using a third-party http_get_header() function, extract the header named APPD_CORRELATION_HEADER_NAME from the incoming HTTP payload.

  2. Pass the header to appd_bt_begin(). For example:

    const char* hdr = http_get_header(req, APPD_CORRELATION_HEADER_NAME);
    appd_bt_handle bt = appd_bt_begin("fraud detection", hdr);
    CPP

If the header retrieved by the http_get_header() function is valid, the business transaction started by the appd_bt_begin() call will be a continuation of the business transaction started by the upstream service.

Correlate with a Downstream Tier

The downstream agent is watching for a correlation header named singularityheader in the HTTP payload.

If your SDK agent is making an exit call to a downstream agent that supports automatic correlation:

  1. Set the name of the correlation header using APPD_CORRELATION_HEADER_NAME.
  2. Begin the exit call.
  3. Retrieve the correlation header from the exit call using the appd_exitcall_get_correlation_header() function.
  4. Using third-party HTTP functions, prepare an HTTP POST request.
  5. Inject a header named APPD_CORRELATION_HEADER_NAME with the value of the correlation header retrieved in step 3 into the outgoing payload of the HTTP request.
  6. Make the request. For example:

    const char* const APPD_CORRELATION_HEADER_NAME = "singularityheader";
    
    appd_exitcall_handle inventory = appd_exitcall_begin(bt, "inventory");
    const char* hdr = appd_exitcall_get_correlation_header(inventory);
    http_request req;
    http_init(&req, HTTP_POST, "https: //inventory/holds/%s", sku);
    http_set_header(&req, APPD_CORRELATION_HEADER_NAME, hdr);
    http_perform(&req);
    
    ...
    CPP

cURL Downstream Correlation Example

The following example uses the cURL library to correlate with a downstream transaction.

#include <curl/curl.h>
. . . 
. . .
// get the correlation header into the response
class LibcurlURLGet
{
public:
    LibcurlURLGet(const std::string& url) : url(url), curlHandle(NULL) {}
    std::string GetResponse(const char* correlationHeader)
    {
        CURLcode res;
        Response response;
        curlHandle = curl_easy_init();
        if (curlHandle)
        {
            curl_easy_setopt(curlHandle, CURLOPT_URL, url.c_str());
            curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, WriteDataCallback);
            curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, &response);
            if(correlationHeader && strlen(correlationHeader))
            {
 
// start new added code
 
                std::string singularityHeader(APPD_CORRELATION_HEADER_NAME);
                singularityHeader.append(": ");
                singularityHeader.append(correlationHeader);
 
// end new added code
 
                curl_slist* slistNewHeaders = 0;
                slistNewHeaders = curl_slist_append(slistNewHeaders, correlationHeader);
                curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, slistNewHeaders);
            }
            res = curl_easy_perform(curlHandle);
            if(res != CURLE_OK)
            {
            }
        }
        curl_easy_cleanup(curlHandle);
        return response.GetResponseString();
    }
private:
    class Response
    {
    public:
        std::string GetResponseString()
        {
            return sResponse;
        }
        void AppendResponse(void* ptr, size_t sizeBytes)
        {
            sResponse.append((char*)ptr, sizeBytes);
        }
    private:
        std::string sResponse;
    };
    static size_t WriteDataCallback(void* ptr, size_t size, size_t nmemb, Response* response)
    {
        response->AppendResponse(ptr, size * nmemb);
        return size * nmemb;
    }
    std::string url;
    CURL* curlHandle;
};
 
...
// start an exit call from the current transaction 
appd_exitcall_handle ecHandle1 = appd_exitcall_begin(btHandle1, tier2);

...
// before exiting get the correlation header
const char* corrHeader1 = appd_exitcall_get_correlation_header(ecHandle1);

// put the correlation in the HTTP response
std::string response = urlGet.GetResponse(corrHeader1);

...
// start the next business transaction to correlate with the previous one
appd_bt_handle btHandle = appd_bt_begin("Verify", response)

CPP

Create Custom Metrics

You can create custom metrics to supplement built-in metrics such as Business Transaction call count and response time. You can also create metrics for multiple application contexts.

Custom metrics are dependent on the application context; in situations with multiple contexts, you must specify the correct context name for custom metrics to apply to the associated application context. See Create Alternate Application Contexts.

If you create a custom metric in a node, the custom metric will appear in every other node in the tier.

For example, the default application context uses a null pointer or empty string as its name. The appd_sdk_init method tells the SDK to connect to the Controller and create the default application context (i.e. no name is specified).

struct appd_config* cfg = appd_config_init();
appd_config_set_controller_host(cfg, host.c_str());
appd_config_set_controller_port(cfg, port);
appd_config_set_controller_account(cfg, account_name.c_str());
appd_config_set_controller_access_key(cfg, access_key.c_str());
appd_config_set_controller_use_ssl(cfg, useSSL?1:0);
appd_config_set_app_name(cfg, app_name.c_str());
appd_config_set_tier_name(cfg, tier_name.c_str());
appd_config_set_node_name(cfg, node_name.c_str());
appd_sdk_init(cfg);
CODE

To specify an alternative application context, you must provide a name string (not a default null pointer). Use the appd_sdk_add_appd_context method to create an alternative application context. In the example below, we create an alternative application context called "context2." You will use that name string to create custom metrics, not the actual configuration structure. 

const char* name = "context2";
struct appd_context_config* cfg2 = appd_context_config_init(name)); 
appd_context_config_set_controller_account(cfg2, ac.second.ac_account_get().c_str());
appd_context_config_set_controller_access_key(cfg2, ac.second.ac_access_key_get().c_str());
appd_context_config_set_controller_host(cfg2, ac.second.ac_host_get().c_str());
appd_context_config_set_controller_port(cfg, ac.second.ac_port_get());
appd_context_config_set_controller_use_ssl(cfg2, ac.second.ac_use_ssl_get() ? 1 : 0);
appd_context_config_set_app_name(cfg2, ac.second.ac_app_get().c_str());
appd_context_config_set_tier_name(cfg2, ac.second.ac_tier_get().c_str());
appd_context_config_set_node_name(cfg2, ac.second.ac_node_get().c_str());
 
appd_sdk_add_app_context(cfg2);
CODE

The Custom Metrics path may or may not go the same Controller; similar to business transactions in separate nodes, your program can process two business transactions from different nodes: one aligns with the default application context and the other with the alternative application context. You must specify that your method strings begin with Custom Metrics|, not Custom Metric|.

You can use the following methods to create a custom metric:

  • appd_custom_metric_add() 

  • appd_custom_metric_report()

The appd_custom_metric_add() method registers a custom metric for the default context.

appd_custom_metric_add(nullptr, "Custom Metrics|my_default_custom_metric" , time_rollup_type, cluster_rollup_type, hole_handling_type);
CODE

The appd_custom_metric_report() creates an application context named "my_default_custom_metric."

appd_custom_metric_report(nullptr, "Custom Metrics|my_default_custom_metric",some_value);
CODE

For an alternative context, the appd_custom_metric_add() method registers a custom metric for the alternative application context.

appd_custom_metric_add(name, "Custom Metrics|my_second_custom_metric" , time_rollup_type, cluster_rollup_type, hole_handling_type);
CODE

or

appd_custom_metric_add("context2", "Custom Metrics|my_second_custom_metric" , time_rollup_type, cluster_rollup_type, hole_handling_type);
CODE

The appd_custom_metric_report creates a default application context and an alternative application context named my_second_custom_metric.

appd_custom_metric_report(name, "Custom Metrics|my_second_custom_metric",some_value);
CODE
appd_custom_metric_report("context2", "Custom Metrics|my_second_custom_metric",some_value);
CODE

In the Controller, the custom metrics folder appears under the associated tier as "Custom Metrics" in the Metric Browser with two entities: my_default_custom_metric and my_second_custom_metric.

The following example illustrates a call with both method signatures.

appd_custom_metric_add("app_context", "Custom Metrics|Memory|Total Memory Usage",
APPD_TIMEROLLUP_TYPE_AVERAGE, APPD_CLUSTERROLLUP_TYPE_INDIVIDUAL,
APPD_HOLEHANDLING_TYPE_RATE_COUNTER);
CODE

The method passes the application context ("Total Memory Usage") and the path to the metric (Custom Metrics > Memory in the Metric Browser) to the Controller. The additional parameters define how the metric is processed. With the metric declared, your code can then report data to the metric with the appd_custom_metric_report() method, as follows: 

appd_custom_metric_report("app_context", "Custom Metrics|Memory|Total Memory Usage", 1234);
CODE

See C/C++ SDK Resource Management to simplify your instrumentation on C++ applications. 

Metric Handling Parameters

Like built-in metrics, the Controller applies some standard processing functions to custom metric values. These functions can define, for example, how the metric is rolled up over time. How the Controller processes a custom metric depends on the nature of the metric; for a metric that represents the state of the machine, the Controller captures the last observed value for the metric. For a call rate metric, the Controller uses an average value. The following section provides information about the parameters you can use to define metric handling by the Controller.

Time Rollup Type

The time_rollup_type parameter tells the Controller how to roll up values for the metric over time. There are three ways in which the Controller can roll up metrics, as follows:

  • It can calculate the average of all reported values in the time period. An example of a built-in metric that uses this is the Average Response Time metric.
  • Sum of all reported values in that minute. This operation behaves like a counter. An example metric is the Calls per Minute metric.
  • Current is the last reported value in the minute. If no value is reported in that minute, the last reported value is used. An example of a metric that uses this would be a machine state metric, such as Max Available (MB) metric.

Cluster Rollup Type

The appd_cluster_rollup_type parameter tells the Controller how to aggregate metric values for the tier—a cluster of nodes.

  • Individual: Aggregates the metric value by averaging the metric values across each node in the tier. For example, Hardware Resources|Memory|Used %  is a built-in metric that uses the individual rollup type.
  • Collective: Aggregates the metric value by adding up the metric values for all the nodes in the tier. For example, Agent|Metric Upload|Metrics uploaded is a built-in metric that uses the collective rollup type. 

Hole Handling Type

A particular metric may not report data for a given minute. The appd_hole_handling_type parameter tells the Controller how to set the metric count for that time slice. The count is set to zero if the hole handling type is REGULAR_COUNTER, and set to one if RATE_COUNTER. In effect, REGULAR_COUNTER does not affect aggregation while RATE_COUNTER does. 

For example, consider four time slices with the data 9, 3, 0, 12. Notice that the third time slice is zero filled, with no metric being reported for that time slice. The sum of the values is 9+3+0+12 = 24.

If the metric is REGULAR_COUNTER, the count for the third times slice would be zero, and therefore the overall count would be 1+1+0+1 = 3. If the metric is RATE_COUNTER the overall count would be 1+1+1+1 = 4. In the case of the REGULAR_COUNTER, the average would be 24/3 = 8, while for the RATE_COUNTER the average would be 24/4 = 6. 

Built-in metrics that use the regular counter hole-handling type include Average Response TimeAverage CPU Used, and Number of Stalls. Built-in metrics that use the rate counter hole-handling type include BT Calls per minute and Errors per minute

Generate Call Graphs

You can use the C/C++ SDK to instrument methods so that they are reported and displayed in the call graph. You can instrument methods in one of the following ways:

  • Use the APPD_AUTO_FRAME macro, C++ only
  • Use appd_frame_begin and appd_frame_end

Instrument Methods with the APPD_AUTO_FRAME Macro

You can use the APPD_AUTO_FRAME macro to instrument applications built on C++ frameworks. The macro is equivalent to calling the appd_frame_beginand appd_frame_end methods.

  1. Specify the business transaction that you want to instrument calls for, and configure the root of the call graph.

    appd::sdk::BT bt("mybt");
    APPD_AUTO_FRAME(bt);
    method1(bt);
    method2(bt);
    CODE

     

  2. Instrument the methods as shown below.

    void method1(appd::sdk::BT& bt)
    {
     APPD_AUTO_FRAME(bt);
     // Code for method1...
    }
    void method2(appd::sdk::BT& bt)
    {
     APPD_AUTO_FRAME(bt);
     // Code for method2...
    }
    CODE

    The methods must have access to the business transaction so the business transaction can be passed into the method as an argument.

Instrument Methods with the appd_frame_begin and appd_frame_end Functions

You can instrument methods for applications that are built on non-C++ frameworks. To instrument a method, you call appd_frame_begin at the beginning of the method, and appd_frame_end at the end of the method. When you do this, the duration of the method call is automatically calculated and reported in the call graph. 

If your method makes an exit call, you can instrument the exit call by wrapping the exit call code between appd_exitcall_begin and appd_exitcall_end.

The code samples below show three methods that are each instrumented using appd_frame_begin and appd_frame_end, and then called in a root method, main(). The second sample method, method2(), contains an exit call.

void method1( )
{
 appd_frame_handle frameHandle = appd_frame_begin(btHandle, APPD_FRAME_TYPE_CPP, nullptr, APPD_FUNCTION_NAME, __FILE__, __LINE__);
 // code for method1
 appd_frame_end(btHandle, frameHandle);
}
CODE
void method2()
{
 appd_frame_handle frameHandle = appd_frame_begin(btHandle, APPD_FRAME_TYPE_CPP, nullptr, APPD_FUNCTION_NAME, __FILE__, __LINE__);
 // code for method2
appd_exitcall_handle exitCallHandle = appd_exitcall_begin(btHandle, BACKEND_NAME);
 // code for exit call
 appd_exitcall_end(exitCallHandle);
 appd_frame_end(btHandle, frameHandle);
}
CODE
void method3()
{
 // To illustrate instrumenting a method where this SDK cannot be used
 // method3 is a wrapper for the call of the actual method which will show up in the call graph
 appd_frame_handle frameHandle = appd_frame_begin(btHandle, APPD_FRAME_TYPE_CPP, "Test", "Compute", "C:\\modules\\source\\Test.cs", 143); 
 // call the wrapped method
 appd_frame_end(btHandle, frameHandle);
}
CODE


The main() method below is the root of the call graph. It specifies the business transaction that invokes the three methods above by passing the transaction into appd_bt_begin and appd_bt_end.

int main(int argc, char **argv)
{
 // initialize AppDynamics
 btHandle = appd_bt_begin(TRANSACTION_NAME, "");
 appd_frame_handle frameHandle = appd_frame_begin(btHandle, APPD_FRAME_TYPE_CPP, nullptr, APPD_FUNCTION_NAME, __FILE__, __LINE__);
 // code for main
 method1();
 method2();
 method3();
 appd_frame_end(btHandle, frameHandle);
 appd_bt_end(btHandle);
}
CODE

The sample methods above are represented by the call graph shown below:

 Call Graph Methods

Generate Pre-populated Call Graphs (C++ Only)

The previous section describes instrumenting your C/C++ application by calling the instrumentation methods in your code. However, if you do not have the option of modifying your source code, you can generate call graphs that are populated with data from sources such as log files.

To generate a pre-populated call graph:

  1. Create the root of the call graph by instantiating a CallGraph class. 
    The CallGraph class takes the following parameters:

    • bt: The business transaction.

    • class_name: The name of the class that contains the root of the call graph.

    • method_name: The name of the method that represents the root of the call graph.
    • file_path: The file path to the class.
    • line_number: The line number of the method.
    • time_msec: The time taken by this frame (method) in milliseconds.
    • frame_type: The type of the frame (the language for the method). Currently, APPD_FRAME_TYPE_CPP is the only option.

    The following example demonstrates a CallGraph instantiation.

    appd::sdk::CallGraph callGraph(bt, "Class1", "main", "/src/file1.cc", 276, 100, APPD_FRAME_TYPE_CPP);
    CODE


  2. Create the call graph tree by calling add_child on the root and on any of the added children:

    callGraph.root() .add_child("Class2", "method1", "/src/file2.cc"), 101, 40, APPD_FRAME_TYPE_CPP) .add_child("Class2", "method2", "/src/file2.cc"), 523, 30, APPD_FRAME_TYPE_CPP); auto& cge1 = callGraph.root() .add_child("Class3", "method1", "/src/file3.cc"), 27, 30, APPD_FRAME_TYPE_CPP); cge1.add_child("Class3", "method2", "/src/file3.cc"), 430, 15, APPD_FRAME_TYPE_CPP);
    CODE
  3. Call add_to_snapshoton the call graph. For this to work, the business transaction must be snapshotting.

    callGraph.add_to_snapshot(); 
    CODE

Analytics Support for C++ SDK

Two of the existing API methods, which were previously only used for adding data to a snapshot, will now send Analytics data if Analytics is enabled for the current Business Transaction on the Controller:

  • Set URL for a snapshot: the URL is set for a snapshot if one is occurring. The data should be either 7-bit ASCII or UTF-8. You can call this function when a snapshot does not occur. When the given Business Transaction is not snapshotting, this function returns immediately. However, if extracting the data to pass to this function is expensive, you can use appd_bt_is_snapshotting to check if the Business Transaction is snapshotting before extracting the data and calling this function.

    void appd_bt_set_url(appd_bt_handle bt, const char* url);
    CODE
    • param bt: The business transaction to add the user data to, if it is taking a snapshot.
    • param url: The value of the URL for the snapshot as 7-bit ASCII or UTF-8.
  • Add user data to a snapshot: the data should be either 7-bit ASCII or UTF-8. You can call this function when a snapshot does not occur or if Analytics is not enabled. If the data is only for snapshotting and extracting the data to pass to this function is expensive, you can use appd_bt_is_snapshotting to check if the Business Transaction is snapshotting before extracting the data, and calling this function.

    void appd_bt_add_user_data(appd_bt_handle bt, const char* key, const char* value);
    CODE


    • param bt: The business transaction to add the user data to, if it is taking a snapshot.
    • param key: The name of the user data to add to the snapshot as 7-bit ASCII or UTF-8.
    • param value: The value of the user data to add to the snapshot as 7-bit ASCII or UTF-8.

These API methods existed previously, but their behavior did not extend to reporting data to Analytics. To correctly connect to Analytics, initialize using the following configuration APIs:

void appd_config_set_analytics_host(struct appd_config* cfg, const char* host); 
CODE
  • param host: The host for the Analytics Agent, which defaults to localhost.
void appd_config_set_analytics_port(struct appd_config* cfg, const unsigned short port);
CODE
  • param port: The port that the Analytics Agent listens on, which defaults to 9090.

Terminate the Agent

Just before the application exits, terminate the SDK:

appd_sdk_term();
CPP