Network topology

  • Latest Dynatrace
  • How-to guide
  • Published May 07, 2026

Starting with Dynatrace version 1.330, you can package OpenPipeline definitions with your extension. This allows you to define the data processing and Smartscape extraction your extension requires and deliver them as one, eliminating the need for maintaining separate configurations of these assets.

Who is this for?

  • Extension developers looking to migrate their topology to OpenPipeline.
  • Network extension developers looking to populate the new Smartscape for network topology model.
Prior knowledge

What will you learn?

This guide walks you through the steps for converting a network extension to use the newest Smartscape topology model. Follow along to learn how to:

  • Create OpenPipeline definitions from within your extension
  • Correctly populate the new Smartscape model for network
  • Adapt your extension to show up correctly in Infrastructure & Operations Infrastructure & Operations

This guide only covers extension content creation. You can use any tool for packaging and signing Dynatrace extensions while you perform the steps.

Scenario

This guide uses a real example: a custom SNMP extension that monitors an F5 load balancer, previously adapted to the classic network topology model.

base_extension.yaml

This is the unmodified extension, used as a starting point. It monitors an F5 load balancer and its network interfaces, and represents these within the classic network topology model.

Show me the base_extension.yaml manifest file
name: custom:f5-load-balancer
version: 1.2.0
minDynatraceVersion: 1.289.0
author:
name: Dynatrace
snmp:
- group: f5
interval:
minutes: 1
dimensions:
- key: instance.name
value: oid:1.3.6.1.2.1.1.5.0
- key: failover.state
value: oid:1.3.6.1.4.1.3375.2.1.14.3.1.0
- key: sync.state
value: oid:1.3.6.1.4.1.3375.2.1.14.1.1.0
- key: monitoring.mode
value: const:Extension
- key: sys.name
value: oid:1.3.6.1.2.1.1.5.0
- key: device.type
value: const:F5 Load balancer
subgroups:
- subgroup: f5-instance-details
table: false
dimensions:
- key: instance.systemname
value: oid:1.3.6.1.4.1.3375.2.1.6.1.0
- key: instance.systemrelease
value: oid:1.3.6.1.4.1.3375.2.1.6.3.0
- key: instance.systemarch
value: oid:1.3.6.1.4.1.3375.2.1.6.5.0
- key: instance.productversion
value: oid:1.3.6.1.4.1.3375.2.1.4.2.0
metrics:
- key: f5.lb.sys.uptime
value: oid:1.3.6.1.4.1.3375.2.1.6.6.0
- key: com.dynatrace.extension.network_device.sysuptime
value: oid:1.3.6.1.4.1.3375.2.1.6.6.0
- subgroup: f5-interface-details
featureSet: interface
table: true
dimensions:
- key: interface.name
value: oid:1.3.6.1.4.1.3375.2.1.2.4.1.2.1.1
- key: if.name
value: oid:1.3.6.1.4.1.3375.2.1.2.4.1.2.1.1
- key: interface.enabled
value: oid:1.3.6.1.4.1.3375.2.1.2.4.1.2.1.8
- key: interface.status
value: oid:1.3.6.1.4.1.3375.2.1.2.4.1.2.1.17
- key: mac.address
value: $networkFormat(const:macAddress, oid:1.3.6.1.4.1.3375.2.1.2.4.1.2.1.6)
metrics:
- key: f5.lb.sys.interface.status
value: const:1
- key: com.dynatrace.extension.network_device.if.status
value: const:1
- subgroup: f5-interface-metrics
featureSet: interface
table: true
dimensions:
- key: interface.name
value: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.1
metrics:
- key: f5.lb.sys.interface.stat.bytes.in.count
value: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.3
type: count
- key: f5.lb.sys.interface.stat.bytes.out.count
value: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.5
type: count
- key: com.dynatrace.extension.network_device.if.bytes_in.count
value: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.3
type: count
- key: com.dynatrace.extension.network_device.if.bytes_out.count
value: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.5
type: count
- key: f5.lb.sys.interface.stat.pkts.in.count
value: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.2
type: count
- key: f5.lb.sys.interface.stat.pkts.out.count
value: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.4
type: count
- subgroup: f5-cpu
table: false
featureSet: instance-cpu
metrics:
- key: com.dynatrace.extension.network_device.cpu_usage
value: oid:1.3.6.1.4.1.3375.2.1.1.2.20.29.0
- key: f5.lb.sys.global.host.cpu.idle1m
value: oid:1.3.6.1.4.1.3375.2.1.1.2.20.25.0
- key: f5.lb.sys.global.host.cpu.iowait1m
value: oid:1.3.6.1.4.1.3375.2.1.1.2.20.28.0
- key: f5.lb.sys.global.host.cpu.irq1m
value: oid:1.3.6.1.4.1.3375.2.1.1.2.20.26.0
- key: f5.lb.sys.global.host.cpu.softirq1min
value: oid:1.3.6.1.4.1.3375.2.1.1.2.20.27.0
- key: f5.lb.sys.global.host.cpu.stolen1m
value: oid:1.3.6.1.4.1.3375.2.1.1.2.20.40.0
- key: f5.lb.sys.global.host.cpu.system1m
value: oid:1.3.6.1.4.1.3375.2.1.1.2.20.24.0
- key: f5.lb.sys.global.host.cpu.user1m
value: oid:1.3.6.1.4.1.3375.2.1.1.2.20.22.0
- subgroup: f5-memory
table: false
featureSet: instance-memory
metrics:
- key: f5.lb.sys.host.memory.total
value: oid:1.3.6.1.4.1.3375.2.1.7.1.1.0
- key: f5.lb.sys.host.memory.used
value: oid:1.3.6.1.4.1.3375.2.1.7.1.2.0
- key: com.dynatrace.extension.network_device.memory_used
value: oid:1.3.6.1.4.1.3375.2.1.7.1.4.0
- key: com.dynatrace.extension.network_device.memory_total
value: oid:1.3.6.1.4.1.3375.2.1.7.1.3.0
topology:
types:
- name: f5lb:instance
displayName: F5 BIG-IP Instance
rules:
- idPattern: f5_instance_{instance.name}
instanceNamePattern: '{instance.name}'
iconPattern: f5
sources:
- sourceType: Metrics
condition: $eq(f5.lb.sys.uptime)
attributes:
- key: dt.ip_addresses
displayName: IP Address
pattern: '{device.address}'
- key: dt.dns_names
displayName: DNS Name
pattern: '{instance.name}'
- key: OSRelease
displayName: OS release
pattern: '{instance.systemrelease}'
- key: OSArchitecture
displayName: OS architecture
pattern: '{instance.systemarch}'
- key: OSName
displayName: OS name
pattern: '{instance.systemname}'
- key: ProductVersion
displayName: Product version
pattern: '{instance.productversion}'
- key: FailoverStatus
pattern: '{failover.state}'
displayName: Failover status
- key: SyncStatus
pattern: '{sync.state}'
displayName: Config sync status
role: default
- idPattern: f5_instance_{instance.name}
instanceNamePattern: '{instance.name}'
iconPattern: f5
sources:
- sourceType: Metrics
condition: $prefix(f5.lb)
requiredDimensions: []
attributes: []
role: default
- name: f5lb:interface
displayName: F5 BIG-IP Interface
rules:
- idPattern: f5_interface_{instance.name}_{interface.name}
instanceNamePattern: '{interface.name}'
iconPattern: network-interfaces
sources:
- sourceType: Metrics
condition: $eq(f5.lb.sys.interface.status)
attributes:
- key: EnabledState
displayName: Enabled State
pattern: '{interface.enabled}'
- key: MacAddress
displayName: MAC Address
pattern: '{mac.address}'
- key: Status
displayName: Status
pattern: '{interface.status}'
role: default
- idPattern: f5_interface_{instance.name}_{interface.name}
instanceNamePattern: '{interface.name}'
iconPattern: network-interfaces
sources:
- sourceType: Metrics
condition: $prefix(f5.lb.sys.interface)
requiredDimensions: []
attributes: []
role: default
- name: network:device
enabled: true
displayName: Network device
rules:
- idPattern: network_device_{device.address}
instanceNamePattern: '{instance.name}'
iconPattern: f5
sources:
- sourceType: Metrics
condition: $eq(f5.lb.sys.uptime) # It's important to target specialized metrics, not the generic ones
attributes:
- key: dt.ip_addresses
displayName: IP Address
pattern: '{device.address}'
- key: dt.dns_names
displayName: DNS Name
pattern: '{instance.name}'
- key: OSRelease
displayName: OS release
pattern: '{instance.systemrelease}'
- key: OSArchitecture
displayName: OS architecture
pattern: '{instance.systemarch}'
- key: OSName
displayName: OS name
pattern: '{instance.systemname}'
- key: ProductVersion
displayName: Product version
pattern: '{instance.productversion}'
- key: FailoverStatus
pattern: '{failover.state}'
displayName: Failover status
- key: SyncStatus
pattern: '{sync.state}'
displayName: Config sync status
role: default
- idPattern: network_device_{device.address}
instanceNamePattern: '{instance.name}'
iconPattern: f5
sources:
- sourceType: Metrics
condition: $prefix(f5.lb)
requiredDimensions: []
attributes: []
role: default
- name: network:interface
enabled: true
displayName: Network interface
rules:
- idPattern: network_interface_{mac.address}_{interface.name}
instanceNamePattern: '{interface.name}'
iconPattern: network-interfaces
sources:
- sourceType: Metrics
condition: $eq(f5.lb.sys.interface.status)
attributes:
- key: EnabledState
displayName: Enabled State
pattern: '{interface.enabled}'
- key: MacAddress
displayName: MAC Address
pattern: '{mac.address}'
- key: ifOperStatus
displayName: Operational status
pattern: '{interface.status}'
role: default
- idPattern: network_interface_{mac.address}_{interface.name}
instanceNamePattern: '{interface.name}'
iconPattern: network-interfaces
sources:
- sourceType: Metrics
condition: $prefix(f5.lb.sys.interface)
requiredDimensions: []
attributes: []
role: default
relationships:
- fromType: f5lb:interface
typeOfRelation: RUNS_ON
toType: f5lb:instance
sources:
- sourceType: Metrics
condition: $prefix(f5.lb.sys.interface)
- fromType: f5lb:interface
typeOfRelation: SAME_AS
toType: network:interface
sources:
- sourceType: Metrics
condition: $prefix(f5.lb.sys.interface)
- fromType: f5lb:instance
typeOfRelation: SAME_AS
toType: network:device
sources:
- sourceType: Metrics
condition: $prefix(f5.lb)
screens:
- entityType: network:device
propertiesCard:
properties:
- type: RELATION
relation:
entitySelectorTemplate: type(f5lb:instance),fromRelationships.isSameAs($(entityConditions))
displayName: F5 Load balancer
conditions:
- relatedEntity|entitySelectorTemplate=type(f5lb:instance),fromRelationships.isSameAs($(entityConditions))
- entityAttribute|devMonitoringMode=Extension
detailsInjections:
- type: CHART_GROUP
key: f5_instance-charts-cpu
entitySelectorTemplate: type(f5lb:instance),fromRelationships.isSameAs($(entityConditions))
conditions:
- relatedEntity|entitySelectorTemplate=type(f5lb:instance),fromRelationships.isSameAs($(entityConditions))
- entityAttribute|devMonitoringMode=Extension
- type: CHART_GROUP
key: network-interfaces-list
chartsCards:
- key: network-interfaces-list
mode: NORMAL
target: BOTH
displayName: Traffic
numberOfVisibleCharts: 1
conditions:
- entityAttribute|devMonitoringMode=Extension
charts:
- displayName: Traffic in/out
visualizationType: GRAPH_CHART
graphChartConfig:
metrics:
- metricSelector: com.dynatrace.extension.network_device.if.bytes_in.count:splitBy("dt.entity.network:device)
dqlQuery: timeseries bytesIn=avg(com.dynatrace.extension.network_device.if.bytes_in.count),by:{`dt.entity.network:device`},filter:{`dt.entity.network:device`==$(entityId)}
visualization:
displayName: Bytes In
- metricSelector: com.dynatrace.extension.network_device.if.bytes_out.count:splitBy("dt.entity.network:device")
dqlQuery: timeseries bytesOut=avg(com.dynatrace.extension.network_device.if.bytes_out.count),by:{`dt.entity.network:device`},filter:{`dt.entity.network:device`==$(entityId)}
visualization:
displayName: Bytes Out
- entityType: f5lb:instance
detailsSettings:
staticContent:
showProblems: true
showProperties: true
showTags: true
showGlobalFilter: true
showAddTag: true
target: BOTH
layout:
autoGenerate: false
cards:
- key: f5_instance-charts-cpu
type: CHART_GROUP
- key: f5_instance-charts-memory
type: CHART_GROUP
chartsCards:
- key: f5_instance-charts-cpu
target: BOTH
mode: NORMAL
displayName: CPU
numberOfVisibleCharts: 4
chartsInRow: 4
charts:
- displayName: CPU Breakdown
visualizationType: GRAPH_CHART
graphChartConfig:
connectGaps: true
stacked: true
metrics:
- metricSelector: f5.lb.sys.global.host.cpu.idle1m:splitBy("dt.entity.f5lb:instance")
dqlQuery: timeseries idle1m=avg(f5.lb.sys.global.host.cpu.idle1m),by:{`dt.entity.f5lb:instance`},filter:{`dt.entity.f5lb:instance`==$(entityId)}
visualization:
displayName: Idle
- metricSelector: f5.lb.sys.global.host.cpu.system1m:splitBy("dt.entity.f5lb:instance")
dqlQuery: timeseries system1m=avg(f5.lb.sys.global.host.cpu.system1m),by:{`dt.entity.f5lb:instance`},filter:{`dt.entity.f5lb:instance`==$(entityId)}
visualization:
displayName: System
- metricSelector: f5.lb.sys.global.host.cpu.user1m:splitBy("dt.entity.f5lb:instance")
dqlQuery: timeseries user1m=avg(f5.lb.sys.global.host.cpu.user1m),by:{`dt.entity.f5lb:instance`},filter:{`dt.entity.f5lb:instance`==$(entityId)}
visualization:
displayName: User
visualization:
themeColor: DEFAULT
seriesType: AREA
- displayName: System CPU
visualizationType: GRAPH_CHART
graphChartConfig:
connectGaps: true
metrics:
- metricSelector: f5.lb.sys.global.host.cpu.system1m:splitBy("dt.entity.f5lb:instance")
dqlQuery: timeseries system1m=avg(f5.lb.sys.global.host.cpu.system1m),by:{`dt.entity.f5lb:instance`},filter:{`dt.entity.f5lb:instance`==$(entityId)}
visualization:
themeColor: BLUE
seriesType: LINE
- displayName: User CPU
visualizationType: GRAPH_CHART
graphChartConfig:
connectGaps: true
metrics:
- metricSelector: f5.lb.sys.global.host.cpu.user1m:splitBy("dt.entity.f5lb:instance")
dqlQuery: timeseries user1m=avg(f5.lb.sys.global.host.cpu.user1m),by:{`dt.entity.f5lb:instance`},filter:{`dt.entity.f5lb:instance`==$(entityId)}
visualization:
themeColor: BLUE
seriesType: LINE
- displayName: Idle CPU
visualizationType: GRAPH_CHART
graphChartConfig:
connectGaps: true
metrics:
- metricSelector: f5.lb.sys.global.host.cpu.idle1m:splitBy("dt.entity.f5lb:instance")
dqlQuery: timeseries idle1m=avg(f5.lb.sys.global.host.cpu.idle1m),by:{`dt.entity.f5lb:instance`},filter:{`dt.entity.f5lb:instance`==$(entityId)}
visualization:
themeColor: BLUE
seriesType: LINE
- key: f5_instance-charts-memory
target: BOTH
mode: NORMAL
displayName: Memory
numberOfVisibleCharts: 4
hideEmptyCharts: true
charts:
- displayName: Memory breakdown
visualizationType: GRAPH_CHART
graphChartConfig:
connectGaps: true
yAxes:
- key: y-absolute
position: LEFT
visible: true
- key: y-relative
position: RIGHT
visible: true
min: '0'
max: '100'
metrics:
- metricSelector: f5.lb.sys.host.memory.total:splitBy("dt.entity.f5lb:instance")
dqlQuery: timeseries total=avg(f5.lb.sys.host.memory.total),by:{`dt.entity.f5lb:instance`},filter:{`dt.entity.f5lb:instance`==$(entityId)}
yAxisKey: y-absolute
visualization:
themeColor: BLUE
seriesType: AREA
displayName: Total
- metricSelector: f5.lb.sys.host.memory.used:splitBy("dt.entity.f5lb:instance")
dqlQuery: timeseries used=avg(f5.lb.sys.host.memory.used),by:{`dt.entity.f5lb:instance`},filter:{`dt.entity.f5lb:instance`==$(entityId)}
yAxisKey: y-absolute
visualization:
themeColor: ORANGE
seriesType: AREA
displayName: Used

Step 1 Key concepts

Let's start by reviewing a couple of key concepts and differences between the classic topology and Smartscape that will define the rest of the steps in this tutorial.

OpenPipeline for extensions

When an extension packages a pipeline definition for OpenPipeline, it must also define an ingest source. This creates a static mapping of the extension's data to the bundled pipeline. This is a key difference compared to the previous network model, as it implies that each extension must define its own processors for populating Smartscape. There are no common processing rules stored with the SNMP Autodiscovery extension as before.

Smartscape for network

The semantic dictionary model for network extensions replaces the classic topology model. You should review the documentation in full. However, these are the key takeaways:

  • Network devices are no longer identified by IP address, but by their chassis MAC address.
  • Network interfaces are no longer identified by a MAC address, meaning the model can now represent virtual/logical interfaces too.
  • The Network port entity is no longer part of the model. The network interface Smartscape node can represent this concept as needed.

Visualizations

Infrastructure & Operations Infrastructure & Operations is the central place for visualizing extension data. The Network devices tab is tailored towards data common to all network devices, whereas you can use the Technologies tab to browse any entity from any extension. We'll explore how extension metadata can affect this tab.

Manifest changes

First, set the value of minDynatraceVersion to 1.330.0. This enables a new top-level attribute in your extension manifest called openpipeline. This attribute links to files packaged within your extension defining OpenPipeline ingest sources and processing pipelines. Place these files—one for a metrics-based ingest source, and one for a metrics-based pipeline—inside a folder with the same name.

openpipeline:
sources:
- displayName: F5 Load Balancer
sourcePath: openpipeline/metrics.source.json
configScope: logs
pipelines:
- displayName: F5 Load Balancer
pipelinePath: openpipeline/metrics.pipeline.json
configScope: logs

The ingest source

Alongside your extension.yaml file (within the extension folder), create a folder called openpipeline with two files inside it called metrics.source.json and metrics.pipeline.json.

The metrics.source.json file defines a static mapping of your extension's metrics to a given pipeline.

{
"displayName": "F5 Load Balancer",
"metadataList": [],
"enabled": true,
"sourceType": "extension",
"source": "custom:f5-load-balancer",
"staticRouting": {
"pipelineType": "custom",
"pipelineId": "extension:custom:f5-load-balancer"
}
}

Note:

  • Use human-readable display names everywhere
  • sourceType must be extension
  • source must be your extension's name
  • pipelineType must be custom (can also be omitted)
  • pipelineId can be anything (just keep it consistent between files)

The processing pipeline

The metrics.pipeline.json file defines the full metrics processing pipeline through which your extension's metrics flow. This can include all supported stages, such as processing and Smartscape node and edge extraction.

Start with the following base structure. The processing array is populated in Processing stage, and the smartscapeNodeExtraction array in Node extraction stage.

{
"displayName": "F5 Load Balancer",
"customId": "extension:custom:f5-load-balancer",
"metadataList": [],
"processing": {
"processors": []
},
"smartscapeNodeExtraction": {
"processors": []
}
}

Note:

  • customId must match the pipelineId from the source file

Step 3 Prepare your extension

Review the extension data

Before you make any changes, review the extension and note a few characteristics that specify the required changes. As part of the update, some entity types may be merged. Also note which metrics carry attribute fields and which metrics need to be enriched with a Smartscape node ID.

  1. Extension details
    • This extension is based on the SNMP datasource
  2. Extension entities
    • f5lb:instance and network:device entities are defined and related same_as. These entities conceptually represent the same thing and will become a Smartscape node of type EXT_NETWORK_DEVICE
    • f5lb:interface and network:interface entities are defined and related same_as. Again, these entities also represent the same concept and will become a Smartscape node of type EXT_NETWORK_INTERFACE
    • The previous runs_on relationship (between f5lb:interface and f5lb:instance, or network:interface and network:device) will become a static edge of type belongs_to between EXT_NETWORK_INTERFACE and EXT_NETWORK_DEVICE
  3. Extension data
    • This extension only ingests metrics
    • The sysuptime metric carries all the network device attribute fields
    • All extension metrics should be enriched with the device node ID
    • The if.status metric carries all the network interface attribute fields
    • Metrics prefixed with f5.lb.sys.interface or com.dynatrace.extension.network_device.if should also be enriched with the interface node ID

We're approaching this from an enterprise perspective with the goal for this extension to fully work on both Dynatrace SaaS and Managed. Otherwise, for a SaaS-only project, you can remove the existing topology and screens definitions from the extension manifest along with duplicate (f5.lb. prefixed) metrics in favor of generic ones (com.dynatrace.extension.network_device. prefixed).

Make adjustments

As part of the network model, a key identifier for both network devices and interfaces is the device chassis MAC address. This can be defined as the first valid, non-zero MAC address on the device.

For SNMP extensions, this logic is implemented and exposed through special property this:device.chassis_mac, which is available starting with Dynatrace version 1.333. Since we concluded earlier that all the extension's metrics should be enriched with the network device node ID, add a chassis.mac dimension to every group defined in the snmp section. For example:

snmp:
- group: f5
interval:
minutes: 1
dimensions:
- key: chassis.mac
value: this:device.chassis_mac

Update minDynatraceVersion to 1.333.0.

Step 4 Define your pipeline

Processing stage

This stage is defined as an array of processors within the processing section of the JSON file (metrics.pipeline.json) created earlier. It's the first stage of the pipeline where you transform incoming signal data before Smartscape extraction.

The Smartscape network model relies on network identification fields common to all Smartscape models. These are defined as arrays, and the ip field is an array of ipAddress type. These data types aren't directly available from the extension framework, so you must process them here.

In addition, because the network model may be populated by multiple integrations, we encourage you to add a troubleshooting.upsert_source field to the signals that generate a Smartscape node event. This helps identify the source of the most recent modification to the node's fields. In the following example, we'll use the format extension:{extension-name}|{signal-type}:{signal-id}.

To achieve this, add the following processors:

{
"id": "reshape-sysuptime-attributes",
"description": "Reshaping of fields on sysuptime metric for Smartscape node field extraction",
"enabled": true,
"type": "dql",
"matcher": "metric.key == \"com.dynatrace.extension.network_device.sysuptime\"",
"dql": {
"script": "fieldsAdd mac=array(chassis.mac), ip=array(toIp(device.address)), troubleshooting.upsert_source=\"extension:f5-load-balancer|metric:sysuptime\""
}
},
{
"id": "reshape-if.status-attributes",
"description": "Reshaping of fields on if.status metric for Smartscape node field extraction",
"enabled": true,
"type": "dql",
"matcher": "metric.key == \"com.dynatrace.extension.network_device.if.status\"",
"dql": {
"script": "fieldsAdd mac=if(isNotNull(mac.address), array(mac.address)), troubleshooting.upsert_source=\"extension:f5-load-balancer|metric:if.status\""
}
}

Node extraction stage

With the data reshaped to match the network model, it's time to extract these fields into Smartscape nodes and edges — all within the smartscapeNodeExtraction section of the JSON file. Note that the separate smartscapeEdgeExtraction stage is for dynamic edges only. Because a network interface node can't exist without a network device, you extract their relationship as a static edge inside the node processor instead.

To optimize processing, we recommend that you separate the processors that result in a Smartscape node event (matching only the signals that carry information about the node and its fields) from the processors that don't (matching all other signals and simply enriching the signal data with the calculated node ID). At the earlier step, you isolated the sysuptime and if.status metrics for this purpose.

Network device nodes

The Smartscape network model requires a single idComponent called identifier for the network device entity. It holds the device's chassis MAC address. In addition, the nodeType must be EXT_NETWORK_DEVICE, and we recommend that you keep dt.smartscape.ext_network_device as the ID-holding field.

First, add the processor that results in a Smartscape node event:

{
"id": "network-device-u-sysuptime",
"description": "Network device upsert from sysuptime metric",
"matcher": "metric.key == \"com.dynatrace.extension.network_device.sysuptime\"",
"enabled": true,
"type": "smartscapeNode",
"smartscapeNode": {
"nodeType": "EXT_NETWORK_DEVICE",
"nodeIdFieldName": "dt.smartscape.ext_network_device",
"idComponents": [
{
"idComponent": "identifier",
"referencedFieldName": "chassis.mac"
}
],
"extractNode": true,
"nodeName": {
"type": "field",
"field": {
"sourceFieldName": "instance.name"
}
},
"fieldsToExtract": [ ]
}
}

The above snippet defines the ID calculation for instances of the EXT_NETWORK_DEVICE node type, enables the Smartscape node event, and provides a name for the node based on the instance.name field.

Within the empty array for fieldsToExtract, you can now add all the fields that you want to update on the node, based on values from the incoming data. For fields that are part of the official model, you should use the exact name. For any other fields, follow these general guidelines: all lowercase, dots to group under domains, and underscores in place of spaces.

The format is:

{
"fieldName": "troubleshooting.upsert_source",
"referencedFieldName": "troubleshooting.upsert_source"
}

In the above example, fieldName is the name of the field on the Smartscape node that holds the value, whereas referencedFieldName is the name of the field (dimension) on the signal (metric) from which the value is taken. The full processor definition with all fields added is shown below.

Show me the full processor
{
"id": "network-device-u-sysuptime",
"description": "Network device upsert from sysuptime metric",
"matcher": "metric.key == \"com.dynatrace.extension.network_device.sysuptime\"",
"enabled": true,
"type": "smartscapeNode",
"smartscapeNode": {
"nodeType": "EXT_NETWORK_DEVICE",
"nodeIdFieldName": "dt.smartscape.ext_network_device",
"idComponents": [
{
"idComponent": "identifier",
"referencedFieldName": "chassis.mac"
}
],
"extractNode": true,
"nodeName": {
"type": "field",
"field": {
"sourceFieldName": "instance.name"
}
},
"fieldsToExtract": [
{
"fieldName": "troubleshooting.upsert_source",
"referencedFieldName": "troubleshooting.upsert_source"
},
{
"fieldName": "mac",
"referencedFieldName": "mac"
},
{
"fieldName": "ip",
"referencedFieldName": "ip"
},
{
"fieldName": "monitoring_mode",
"referencedFieldName": "monitoring.mode"
},
{
"fieldName": "device_type",
"referencedFieldName": "device.type"
},
{
"fieldName": "failover_state",
"referencedFieldName": "failover.state"
},
{
"fieldName": "sync_state",
"referencedFieldName": "sync.state"
},
{
"fieldName": "os.name",
"referencedFieldName": "instance.systemname"
},
{
"fieldName": "os.release",
"referencedFieldName": "instance.systemrelease"
},
{
"fieldName": "os.architecture",
"referencedFieldName": "instance.systemarch"
},
{
"fieldName": "product_version",
"referencedFieldName": "instance.productversion"
}
]
}
}

Our next processor still handles the network device node, but doesn't result in a Smartscape node event. This means the node ID is calculated and inserted into the signal data, associating those metrics with the node.

{
"id": "network-device-metrics",
"description": "Network device ID extraction on all extension metrics",
"matcher": "isNotNull(chassis.mac)",
"enabled": true,
"type": "smartscapeNode",
"smartscapeNode": {
"nodeType": "EXT_NETWORK_DEVICE",
"nodeIdFieldName": "dt.smartscape.ext_network_device",
"idComponents": [
{
"idComponent": "identifier",
"referencedFieldName": "chassis.mac"
}
],
"extractNode": false
}
}

OpenPipeline implicitly adds an isNotNull() check to each processor's matcher, for every field that's part of idComponents or nodeName. In the above example, we want to match all of the extension's metrics, so the matcher is redundant. isNotNull(chassis.mac), isNotNull(metric.key), and true are all equivalent matchers for this purpose.

Network interface nodes

Now repeat the process for the network interface node. Here, the type is EXT_NETWORK_INTERFACE and it requires two idComponents called name and device.identifier, which hold the interface name and the underlying device's chassis MAC address respectively. As before, start with the processor that generates a node event.

Leave the fieldsToExtract array empty for brevity and instead focus on a new attribute called staticEdgesToExtract. The network model defines the relationship between an interface and a device as a static edge called belongs_to. The source of the edge is the node you're already processing. To complete the edge definition, you must provide details of the target node.

The processor looks like this:

{
"id": "network-interface-u-status",
"description": "Network interface upsert from status metric",
"matcher": "metric.key == \"com.dynatrace.extension.network_device.if.status\"",
"enabled": true,
"type": "smartscapeNode",
"smartscapeNode": {
"nodeType": "EXT_NETWORK_INTERFACE",
"nodeIdFieldName": "dt.smartscape.ext_network_interface",
"idComponents": [
{
"idComponent": "name",
"referencedFieldName": "if.name"
},
{
"idComponent": "device.identifier",
"referencedFieldName": "chassis.mac"
}
],
"extractNode": true,
"nodeName": {
"type": "field",
"field": {
"sourceFieldName": "if.name"
}
},
"fieldsToExtract": [ ],
"staticEdgesToExtract": [
{
"edgeType": "belongs_to",
"targetType": "EXT_NETWORK_DEVICE",
"targetIdFieldName": "dt.smartscape.ext_network_device"
}
]
}
}

Now add all fields to extract on the interface node. Then add another processor that doesn't result in a Smartscape node event, but instead enriches all metrics prefixed with f5.lb.sys.interface or com.dynatrace.extension.network_device.if with the interface node ID.

Show me the full pipeline
{
"displayName": "F5 Load Balancer",
"customId": "extension:custom:f5-load-balancer",
"metadataList": [],
"processing": {
"processors": [
{
"id": "reshape-sysuptime-attributes",
"description": "Reshaping of fields on sysuptime metric for Smartscape node field extraction",
"enabled": true,
"type": "dql",
"matcher": "metric.key == \"com.dynatrace.extension.network_device.sysuptime\"",
"dql": {
"script": "fieldsAdd mac=array(chassis.mac), ip=array(toIp(device.address)), troubleshooting.upsert_source=\"extension:f5-load-balancer|metric:sysuptime\""
}
},
{
"id": "reshape-if.status-attributes",
"description": "Reshaping of fields on if.status metric for Smartscape node field extraction",
"enabled": true,
"type": "dql",
"matcher": "metric.key == \"com.dynatrace.extension.network_device.if.status\"",
"dql": {
"script": "fieldsAdd mac=if(isNotNull(mac.address), array(mac.address)), troubleshooting.upsert_source=\"extension:f5-load-balancer|metric:if.status\""
}
}
]
},
"smartscapeNodeExtraction": {
"processors": [
{
"id": "network-device-u-sysuptime",
"description": "Network device upsert from sysuptime metric",
"matcher": "metric.key == \"com.dynatrace.extension.network_device.sysuptime\"",
"enabled": true,
"type": "smartscapeNode",
"smartscapeNode": {
"nodeType": "EXT_NETWORK_DEVICE",
"nodeIdFieldName": "dt.smartscape.ext_network_device",
"idComponents": [
{
"idComponent": "identifier",
"referencedFieldName": "chassis.mac"
}
],
"extractNode": true,
"nodeName": {
"type": "field",
"field": {
"sourceFieldName": "instance.name"
}
},
"fieldsToExtract": [
{
"fieldName": "troubleshooting.upsert_source",
"referencedFieldName": "troubleshooting.upsert_source"
},
{
"fieldName": "mac",
"referencedFieldName": "mac"
},
{
"fieldName": "ip",
"referencedFieldName": "ip"
},
{
"fieldName": "monitoring_mode",
"referencedFieldName": "monitoring.mode"
},
{
"fieldName": "device_type",
"referencedFieldName": "device.type"
},
{
"fieldName": "failover_state",
"referencedFieldName": "failover.state"
},
{
"fieldName": "sync_state",
"referencedFieldName": "sync.state"
},
{
"fieldName": "os.name",
"referencedFieldName": "instance.systemname"
},
{
"fieldName": "os.release",
"referencedFieldName": "instance.systemrelease"
},
{
"fieldName": "os.architecture",
"referencedFieldName": "instance.systemarch"
},
{
"fieldName": "product_version",
"referencedFieldName": "instance.productversion"
}
]
}
},
{
"id": "network-device-metrics",
"description": "Network device ID extraction on all extension metrics",
"matcher": "isNotNull(chassis.mac)",
"enabled": true,
"type": "smartscapeNode",
"smartscapeNode": {
"nodeType": "EXT_NETWORK_DEVICE",
"nodeIdFieldName": "dt.smartscape.ext_network_device",
"idComponents": [
{
"idComponent": "identifier",
"referencedFieldName": "chassis.mac"
}
],
"extractNode": false
}
},
{
"id": "network-interface-u-status",
"description": "Network interface upsert from status metric",
"matcher": "metric.key == \"com.dynatrace.extension.network_device.if.status\"",
"enabled": true,
"type": "smartscapeNode",
"smartscapeNode": {
"nodeType": "EXT_NETWORK_INTERFACE",
"nodeIdFieldName": "dt.smartscape.ext_network_interface",
"idComponents": [
{
"idComponent": "name",
"referencedFieldName": "if.name"
},
{
"idComponent": "device.identifier",
"referencedFieldName": "chassis.mac"
}
],
"extractNode": true,
"nodeName": {
"type": "field",
"field": {
"sourceFieldName": "if.name"
}
},
"fieldsToExtract": [
{
"fieldName": "troubleshooting.upsert_source",
"referencedFieldName": "troubleshooting.upsert_source"
},
{
"fieldName": "mac",
"referencedFieldName": "mac"
},
{
"fieldName": "operational_status",
"referencedFieldName": "interface.status"
},
{
"fieldName": "is_enabled",
"referencedFieldName": "interface.enabled"
}
],
"staticEdgesToExtract": [
{
"edgeType": "belongs_to",
"targetType": "EXT_NETWORK_DEVICE",
"targetIdFieldName": "dt.smartscape.ext_network_device"
}
]
}
},
{
"id": "network-interface-metrics",
"description": "Network interface ID extraction on all interface metrics",
"matcher": "matchesValue(metric.key, \"com.dynatrace.extension.network_device.if.*\") or matchesValue(metric.key, \"f5.lb.sys.interface.*\")",
"enabled": true,
"type": "smartscapeNode",
"smartscapeNode": {
"nodeType": "EXT_NETWORK_INTERFACE",
"nodeIdFieldName": "dt.smartscape.ext_network_interface",
"idComponents": [
{
"idComponent": "name",
"referencedFieldName": "if.name"
},
{
"idComponent": "device.identifier",
"referencedFieldName": "chassis.mac"
}
],
"extractNode": true
}
}
]
}
}

Step 5 Verify your results

Once you have packaged, signed, and uploaded your extension to your Dynatrace environment, you can verify your work.

OpenPipeline assets

  1. Go to Settings Settings > Process and contextualize > OpenPipeline > Metrics > Ingest sources
    • Check that your "F5 Load Balancer" source was created, along with a route to a pipeline with the same name
  2. Go to Settings Settings > Process and contextualize > OpenPipeline > Metrics > Pipelines
    • Check that your "F5 Load Balancer" pipeline was created
    • Browse through its stages to check all processors are as expected

Smartscape nodes

Once your extension is configured and starts collecting data, go to Infrastructure & Operations Infrastructure & Operations > Network devices to view the network devices created.

  • You can filter by Discovered type, which maps to your Smartscape nodes' device_type field value
  • Selecting a device displays a side panel where you can browse the device's network interfaces

FAQ

I'm not using the SNMP datasource. How do I find the chassis MAC address?

For network device identification, the chassis MAC address is defined as the first valid, non-zero physical address available on the device's network interface controllers (NICs). This is also referred to as the "burned-in" address.

What if my device doesn't report a MAC address?

The new network model is flexible enough to allow for this use case too. The idComponent name is purposefully kept vague as identifier instead of directly mentioning a MAC address. You can provide any other value your device may provide to uniquely identify it, such as a UUID or FQDN.

Are there any drawbacks to not using a chassis MAC as identifier?

The only drawback is if your device can be discovered by the SNMP Autodiscovery extension. A duplicate "Discovered" device node is created during device discovery, while during neighbor discovery, a duplicate "Neighbor" device node is created.

Related tags
Infrastructure Observability