Write your first OneAgent plugin

Welcome to the OneAgent custom plugin documentation. These lessons will guide you step-by-step through the process of creating your first OneAgent plugin. The entire process is summarized in the following illustration:

_images/custom_plugin_development.png

Set up your environment

To develop custom plugins for OneAgent, you first need to set up your environment.

Prerequisites

  1. OneAgent installed and running on your system
  2. Python 3.6
  3. plugin-sdk python package installed

Obtaining OneAgent Plugin SDK

Download the OneAgent Plugin SDK from the Dynatrace UI.

  1. Go to Settings > Monitoring > Monitored technologies.
  2. Click the Add new technology monitoring button.
  3. In the Monitor any technology section, click the Add OneAgent plugin button.
  4. You will see a high level overview of the steps required to develop your plugin, along with the SDK download link.
_images/download_sdk.png

Installing OneAgent Plugin SDK

The OneAgent Plugin SDK is shipped as a ZIP archive that contains this documentation and a wheel package (WHL format). You can install the SDK wheel package using pip3, for example:

pip3 install plugin_sdk-1.98.0.20160601.192338-py3-none-any.whl

If you need help installing Python packages, please see the Python Packaging User Guide

Note

If you’re short on time, or get stuck, the plugin source code is available in the directory examples/demo_plugin of your SDK package

Your first plugin

The first plugin you’ll write gathers data from a simple Python web application. The application itself is provided as part of the SDK package and can be run by typing:

python3 -m plugin_sdk.demo_app

When run without parameters, the application listens on localhost:8769, and responds to HTTP requests with a small JSON document that looks like this:

{"counter": 147, "random": 907}

counter increases with every hit to the page, while random is a random number.

You can list the parameters of the demo application using:

python3 -m plugin_sdk.demo_app --help

This includes the parameters for changing the default port and turning on authentication.

Let’s take a look at the plugin. Essentially it contains 2 files: plugin.json which includes the metadata required to run the plugin (including name and version) and the Python code, which is in the demo_plugin.py file. Let’s go through the PY file first:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import requests
import json
import logging
from ruxit.api.base_plugin import BasePlugin
from ruxit.api.snapshot import pgi_name


class DemoPlugin(BasePlugin):
    def query(self, **kwargs):
        pgi = self.find_single_process_group(pgi_name('plugin_sdk.demo_app'))
        pgi_id = pgi.group_instance_id
        stats_url = "http://localhost:8769"
        stats = json.loads(requests.get(stats_url).content.decode())
        self.results_builder.absolute(key='random', value=stats['random'], entity_id=pgi_id)
        self.results_builder.relative(key='counter', value=stats['counter'], entity_id=pgi_id)

The file defines one class DemoPlugin, which in turn defines one method query. When your plugin runs, this method is called once each minute to collect and send data to Dynatrace Server. This particular query method does the following:

        pgi = self.find_single_process_group(pgi_name('plugin_sdk.demo_app'))
        pgi_id = pgi.group_instance_id

All measurements in OneAgent must specify the ID of the entity that they’re associated with. A process snapshot is a data structure that contains a mapping between running processes and their IDs as assigned by OneAgent. To send data, Dynatrace uses the following method to search the snapshot:

Learn more about exploring process snapshots.

        stats_url = "http://localhost:8769"
        stats = json.loads(requests.get(stats_url).content.decode())

Once Dynatrace acquires the ID and ensures that our demo application is running and has been detected by OneAgent, it’s time to gather some sample data. This is easily achieved with a requests package.

        self.results_builder.absolute(key='random', value=stats['random'], entity_id=pgi_id)
        self.results_builder.relative(key='counter', value=stats['counter'], entity_id=pgi_id)

Once Dynatrace has both the sample data to be reported and the relevant entity IDs, Dynatrace sends the data using the results_builder object.

ResultsBuilder stores and processes all data sent to it. When you submit measurements as relative, Dynatrace automatically computes the difference between current and previous values and only sends the difference. This approach is useful for reporting on metrics that increase most of the time and you’re only interested in the rate of change, not the absolute values.

That’s it for the Python part of the plugin. Note that while the demo plugin only includes a single file for the sake of simplicity, plugins are not limited to a single source file.

The other vital part of each custom plugin is the plugin.json file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
  "name": "custom.python.demo_plugin",
  "version": "1.2",
  "type": "python",
  "entity": "PROCESS_GROUP_INSTANCE",
  "processTypeNames": ["PYTHON"],
  "source": {
    "package": "demo_plugin",
    "className": "DemoPlugin",
    "install_requires": ["requests>=2.6.0"],
    "activation": "Singleton"
  },
  "metrics": [
    {
      "timeseries": {
        "key": "random",
        "unit": "Count",
	"displayname": "Random Value"
      }
    },
    {
      "timeseries": {
        "key": "counter",
        "unit": "Count",
	"displayname":"Counter Value"
      }
    }
  ],
   "configUI": {
        "displayName": "OneAgent Demo Plugin"
    }
}

Let’s go through the contents of this file.

  "name": "custom.python.demo_plugin",
  "version": "1.2",
  "type": "python",

There are a few simple properties at the beginning: plugin name, version, and type. Python custom plugin names should follow ^custom.python(\.[a-zA-Z0-9_]+)+$ regex. For example, custom.python.my_plugin_2. Please ensure that the name of each plugin is unique. Dynatrace won’t accept two plugins that have the same name. Version is fairly self explanatory, type describes the plugin technology, which for custom plugins is always set to `python.

  "entity": "PROCESS_GROUP_INSTANCE",
  "processTypeNames": ["PYTHON"],

entity and processTypeNames are used to tell OneAgent when it should execute the plugin. In this case, the values state that the plugin should be executed when the process groups identified by Python (process type 29) are detected.

  "source": {
    "package": "demo_plugin",
    "className": "DemoPlugin",
    "install_requires": ["requests>=2.6.0"],
    "activation": "Singleton"
  },

The source section covers information specific to a given plugin type, in this case Python. Plugins written in Python don’t necessarily monitor Python. The first two lines list the package name and class that is to be imported and executed by OneAgent. This corresponds to the name of the Python file that contains the code and the name of the class that’s defined in it.

The property install_requires lists the plugin’s dependency, the requests library. Other libraries, along with their version requirements, can be listed here. If you’re familiar with the Python packaging ecosystem, you’ll note that these properties are similar to what you would write in your setup.py or requirements.txt files when developing a Python package.

The last property activation is an additional parameter that controls the plugin activation process. Only one plugin class instance is created, no matter how many process groups of type Python are detected on the system.

  "metrics": [
    {
      "timeseries": {
        "key": "random",
        "unit": "Count",
	"displayname": "Random Value"
      }
    },
    {
      "timeseries": {
        "key": "counter",
        "unit": "Count",
	"displayname":"Counter Value"
      }
    }
  ],

Lastly, the metrics section describes the data that’s gathered by the plugin. This section provides two metrics that mirror both what our demo application serves, and also what the Python code collects.

Learn more about plugin.json

Plugin simulator

To facilitate plugin development, this SDK comes with a tool that can execute your plugin without the need for you to run OneAgent or connect to Dynatrace Server. This plugin simulator is a purely Python solution that’s designed to mimic OneAgent, with a few limitations:

  • The process snapshot must be provided manually.
  • Plugin configuration (if any) needs to be provided manually.
  • No data is sent to or received from Dynatrace Server.
  • The Python environment contains the libraries required by the plugin.

The format used for both snapshot and properties configuration is JSON. Following is an example simulator_snapshot.json file that is sufficient for triggering the demo plugin:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "entries": [
    {
      "process_type": 29,
      "group_name": "plugin_sdk.demo_app",
      "processes": [
        {
          "process_name": "python3.5",
          "properties": {
            "CmdLine": "-m plugin_sdk.demo_app",
            "WorkDir": "/home/demo",
            "ListeningPorts": "8769"
          }
        }
      ]
    }
  ]
}

The structure of simulator_snapshot.json resembles ProcessSnapshot. The fields that aren’t required at the moment, or that can be generated by the simulator (for example, process group IDs), can be omitted. In this particular snapshot you can see one process group of type 29 (Python) with one process attached. This should be enough to run the demo plugin, provided that the demo_app is running.

The simulator is started with the command:

oneagent_simulate_plugin

Or the shorter version:

oneagent_sim

No additional arguments are needed if the command is run from the directory that contains the plugin. For other options, please refer to the command help message.

The simulator runs until it is stopped manually (usually by pressing Ctrl-C), output is written to the terminal, and a short summary of operations is provided upon completion, which should look something like this:

Plugin simulator summary:
At 14:23:52 reported measurements:
[PluginMeasurement(monitoredEntityId=1, tsIdentifier='custom.python.demo_plugin:random', dimensions=[], value=56.0, aggregation=1)]
At 14:23:52 reported status: OK
At 14:24:52 reported measurements:
[PluginMeasurement(monitoredEntityId=1, tsIdentifier='custom.python.demo_plugin:counter', dimensions=[], value=1.0, aggregation=1),
 PluginMeasurement(monitoredEntityId=1, tsIdentifier='custom.python.demo_plugin:random', dimensions=[], value=313.0, aggregation=1)]
At 14:25:52 reported measurements:
[PluginMeasurement(monitoredEntityId=1, tsIdentifier='custom.python.demo_plugin:counter', dimensions=[], value=1.0, aggregation=1),
 PluginMeasurement(monitoredEntityId=1, tsIdentifier='custom.python.demo_plugin:random', dimensions=[], value=567.0, aggregation=1)]

If an error occurs, the summary may offer a hint as to why:

At 14:28:30 reported status: UNINITIALIZED
At 14:28:30 reported status: ERROR_UNKNOWN
Status details:
Traceback (most recent call last):
  File "C:\work\envs\plugins_dev_35\lib\site-packages\requests\packages\urllib3\connection.py", line 137, in _new_conn
    (self.host, self.port), self.timeout, **extra_kw)
  File "C:\work\envs\plugins_dev_35\lib\site-packages\requests\packages\urllib3\util\connection.py", line 91, in create_connection
    raise err
  File "C:\work\envs\plugins_dev_35\lib\site-packages\requests\packages\urllib3\util\connection.py", line 81, in create_connection
    sock.connect(sa)
ConnectionRefusedError: [WinError 10061] No connection could be made because the target machine actively refused it

In this example, the error was caused by not having a running plugin_sdk.demo_app process. So the plugin was unable to gather data.

The report at the end of the simulator run is not the only source of information. You can also analyze log messages that are sent out while the simulator runs.

Run the plugin

By now, you should have a grasp of what the demo plugin can do, but you haven’t run it yet! It’s super easy; only 3 steps are required:

  1. Build the plugin.
  2. Upload the plugin to Dynatrace Server.
  3. Restart the plugin.

Build the plugin

The first step is easily accomplished with the help of the oneagent_build_plugin --no_upload command, which is available in the SDK. Just type:

oneagent_build_plugin --no_upload

While in the directory that contains the files you worked on in the previous section, you should see output similar to this:

Starting oneagent_build_plugin
Validating plugin.json against schema
Plugin data: Plugin name=custom.python.demo_plugin, version=1.0
...
========================================
Plugin deployed successfully into /opt/dynatrace/oneagent/plugin_development/custom.python.demo_plugin
Plugin archive available at /opt/dynatrace/oneagent/plugin_development/custom.python.demo_plugin.zip
========================================

Note

Running the command requires write privileges for the target directory, usually opt/dynatrace/oneagent on Linux and c:\\Program Files(x86)\\dynatrace\\oneagent on Windows

Also, the oneagent_build_plugin command has a few options available that can be listed by issuing:

oneagent_build_plugin --help

These include options for specifying the source and target directories of the tool.

Note

Make sure that a plugin is built on the same OS platform as the plugin is going to be deployed. For example:
  • If your plugin is going to be deployed on 32-bit Linux machine it has to be built on 32-bit Linux machine using 32-bit Python 3.6 interpreter,
  • If your plugin is going to be deployed on 64-bit Windows machine it has to be built on 64-bit Windows machine using 64-bit Python 3.6 interpreter,
  • etc.

The plugin package is now available for upload to the server.

Upload the plugin

To upload the plugin, complete the wokrflow you started with downloading the SDK

  1. Go to Settings > Monitoring > Monitored technologies.
  2. Click the Add new technology monitoring button.
  3. In the Monitor any technology section, click the Add OneAgent plugin button.
  4. You will see a high level overview of the steps required to develop your plugin, along with the Upload plugin button.
_images/download_sdk.png

The plugin should be uploaded in staging mode and visible on the Monitored technologies page.

_images/demo_01_staging_plugin.png

Alternatively, the oneagent_upload_plugin commandline tool may be used to perform the upload. To use this you must first acquire an authentication token.

The token is generated via the Dynatrace web UI using the Generate key button, which is located next to the Upload plugin button on the Custom plugins tab (see above). The token may than be stored in a local file as an environment variable or passed directly to the upload tool:

oneagent_upload_plugin -t pEnLDvdMTA2lhJ_IUNC8U

If the upload is successful, you should see an output resembling this:

Starting new HTTPS connection (1): demo.live.ruxit.com
plugin has been uploaded successfully

To learn more about the commandline upload, please type:

oneagent_upload_plugin --help

If you prefer, you can build, upload, and restart using a single command. Just type oneagent_build_plugin. This will build the plugin, ask for the token, upload the plugin to server, and restart OneAgent.

Promote plugin to production use

Once the plugin is successfully uploaded, you may decide to promote the plugin to production mode.

  1. Navigate to plugin and click the Upgrade now button.

    _images/demo_01_promote_plugin1.png
  2. Upload the folder generated by the oneagent_build_plugin tool to the plugin_deployment folder in the root of your OneAgent installation (Usually /opt/dynatrace/oneagent or C:\\Program Files (x86)\\dynatrace\\oneagent).

  3. Restart the agent.

The last step is to restart OneAgent so that it can load and run the new plugin. Depending on your operating system, restarting OneAgent can be done in a few different ways. On Ubuntu, type:

sudo service oneagent restart

On Windows, go to Service Manager and restart the OneAgent service, or type:

net stop "Dynatrace OneAgent" && net start "Dynatrace OneAgent"

Verify that the plugin works

To verify that the plugin works, make sure that the demo_app is running. Navigate to the app’s host details page (see below), find the host you’re working on, and click the plugin_sdk.demo_app process.

_images/demo_01_host.png

From there, click Further details.

_images/demo_01_pgi.png

Here, you can see the metrics:

_images/demo_01_plugin_metrics.png

Congratulations, you’ve written your first custom OneAgent plugin! If you ran into any issues, please consult the troubleshooting guide.

Next steps

The capabilities of custom OneAgent plugins don’t end with monitoring demo applications. To learn more, please explore these additional resources: