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.
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:
Infrastructure & OperationsThis guide only covers extension content creation. You can use any tool for packaging and signing Dynatrace extensions while you perform the steps.
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.
base_extension.yaml manifest filename: custom:f5-load-balancerversion: 1.2.0minDynatraceVersion: 1.289.0author:name: Dynatracesnmp:- group: f5interval:minutes: 1dimensions:- key: instance.namevalue: oid:1.3.6.1.2.1.1.5.0- key: failover.statevalue: oid:1.3.6.1.4.1.3375.2.1.14.3.1.0- key: sync.statevalue: oid:1.3.6.1.4.1.3375.2.1.14.1.1.0- key: monitoring.modevalue: const:Extension- key: sys.namevalue: oid:1.3.6.1.2.1.1.5.0- key: device.typevalue: const:F5 Load balancersubgroups:- subgroup: f5-instance-detailstable: falsedimensions:- key: instance.systemnamevalue: oid:1.3.6.1.4.1.3375.2.1.6.1.0- key: instance.systemreleasevalue: oid:1.3.6.1.4.1.3375.2.1.6.3.0- key: instance.systemarchvalue: oid:1.3.6.1.4.1.3375.2.1.6.5.0- key: instance.productversionvalue: oid:1.3.6.1.4.1.3375.2.1.4.2.0metrics:- key: f5.lb.sys.uptimevalue: oid:1.3.6.1.4.1.3375.2.1.6.6.0- key: com.dynatrace.extension.network_device.sysuptimevalue: oid:1.3.6.1.4.1.3375.2.1.6.6.0- subgroup: f5-interface-detailsfeatureSet: interfacetable: truedimensions:- key: interface.namevalue: oid:1.3.6.1.4.1.3375.2.1.2.4.1.2.1.1- key: if.namevalue: oid:1.3.6.1.4.1.3375.2.1.2.4.1.2.1.1- key: interface.enabledvalue: oid:1.3.6.1.4.1.3375.2.1.2.4.1.2.1.8- key: interface.statusvalue: oid:1.3.6.1.4.1.3375.2.1.2.4.1.2.1.17- key: mac.addressvalue: $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.statusvalue: const:1- key: com.dynatrace.extension.network_device.if.statusvalue: const:1- subgroup: f5-interface-metricsfeatureSet: interfacetable: truedimensions:- key: interface.namevalue: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.1metrics:- key: f5.lb.sys.interface.stat.bytes.in.countvalue: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.3type: count- key: f5.lb.sys.interface.stat.bytes.out.countvalue: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.5type: count- key: com.dynatrace.extension.network_device.if.bytes_in.countvalue: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.3type: count- key: com.dynatrace.extension.network_device.if.bytes_out.countvalue: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.5type: count- key: f5.lb.sys.interface.stat.pkts.in.countvalue: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.2type: count- key: f5.lb.sys.interface.stat.pkts.out.countvalue: oid:1.3.6.1.4.1.3375.2.1.2.4.4.3.1.4type: count- subgroup: f5-cputable: falsefeatureSet: instance-cpumetrics:- key: com.dynatrace.extension.network_device.cpu_usagevalue: oid:1.3.6.1.4.1.3375.2.1.1.2.20.29.0- key: f5.lb.sys.global.host.cpu.idle1mvalue: oid:1.3.6.1.4.1.3375.2.1.1.2.20.25.0- key: f5.lb.sys.global.host.cpu.iowait1mvalue: oid:1.3.6.1.4.1.3375.2.1.1.2.20.28.0- key: f5.lb.sys.global.host.cpu.irq1mvalue: oid:1.3.6.1.4.1.3375.2.1.1.2.20.26.0- key: f5.lb.sys.global.host.cpu.softirq1minvalue: oid:1.3.6.1.4.1.3375.2.1.1.2.20.27.0- key: f5.lb.sys.global.host.cpu.stolen1mvalue: oid:1.3.6.1.4.1.3375.2.1.1.2.20.40.0- key: f5.lb.sys.global.host.cpu.system1mvalue: oid:1.3.6.1.4.1.3375.2.1.1.2.20.24.0- key: f5.lb.sys.global.host.cpu.user1mvalue: oid:1.3.6.1.4.1.3375.2.1.1.2.20.22.0- subgroup: f5-memorytable: falsefeatureSet: instance-memorymetrics:- key: f5.lb.sys.host.memory.totalvalue: oid:1.3.6.1.4.1.3375.2.1.7.1.1.0- key: f5.lb.sys.host.memory.usedvalue: oid:1.3.6.1.4.1.3375.2.1.7.1.2.0- key: com.dynatrace.extension.network_device.memory_usedvalue: oid:1.3.6.1.4.1.3375.2.1.7.1.4.0- key: com.dynatrace.extension.network_device.memory_totalvalue: oid:1.3.6.1.4.1.3375.2.1.7.1.3.0topology:types:- name: f5lb:instancedisplayName: F5 BIG-IP Instancerules:- idPattern: f5_instance_{instance.name}instanceNamePattern: '{instance.name}'iconPattern: f5sources:- sourceType: Metricscondition: $eq(f5.lb.sys.uptime)attributes:- key: dt.ip_addressesdisplayName: IP Addresspattern: '{device.address}'- key: dt.dns_namesdisplayName: DNS Namepattern: '{instance.name}'- key: OSReleasedisplayName: OS releasepattern: '{instance.systemrelease}'- key: OSArchitecturedisplayName: OS architecturepattern: '{instance.systemarch}'- key: OSNamedisplayName: OS namepattern: '{instance.systemname}'- key: ProductVersiondisplayName: Product versionpattern: '{instance.productversion}'- key: FailoverStatuspattern: '{failover.state}'displayName: Failover status- key: SyncStatuspattern: '{sync.state}'displayName: Config sync statusrole: default- idPattern: f5_instance_{instance.name}instanceNamePattern: '{instance.name}'iconPattern: f5sources:- sourceType: Metricscondition: $prefix(f5.lb)requiredDimensions: []attributes: []role: default- name: f5lb:interfacedisplayName: F5 BIG-IP Interfacerules:- idPattern: f5_interface_{instance.name}_{interface.name}instanceNamePattern: '{interface.name}'iconPattern: network-interfacessources:- sourceType: Metricscondition: $eq(f5.lb.sys.interface.status)attributes:- key: EnabledStatedisplayName: Enabled Statepattern: '{interface.enabled}'- key: MacAddressdisplayName: MAC Addresspattern: '{mac.address}'- key: StatusdisplayName: Statuspattern: '{interface.status}'role: default- idPattern: f5_interface_{instance.name}_{interface.name}instanceNamePattern: '{interface.name}'iconPattern: network-interfacessources:- sourceType: Metricscondition: $prefix(f5.lb.sys.interface)requiredDimensions: []attributes: []role: default- name: network:deviceenabled: truedisplayName: Network devicerules:- idPattern: network_device_{device.address}instanceNamePattern: '{instance.name}'iconPattern: f5sources:- sourceType: Metricscondition: $eq(f5.lb.sys.uptime) # It's important to target specialized metrics, not the generic onesattributes:- key: dt.ip_addressesdisplayName: IP Addresspattern: '{device.address}'- key: dt.dns_namesdisplayName: DNS Namepattern: '{instance.name}'- key: OSReleasedisplayName: OS releasepattern: '{instance.systemrelease}'- key: OSArchitecturedisplayName: OS architecturepattern: '{instance.systemarch}'- key: OSNamedisplayName: OS namepattern: '{instance.systemname}'- key: ProductVersiondisplayName: Product versionpattern: '{instance.productversion}'- key: FailoverStatuspattern: '{failover.state}'displayName: Failover status- key: SyncStatuspattern: '{sync.state}'displayName: Config sync statusrole: default- idPattern: network_device_{device.address}instanceNamePattern: '{instance.name}'iconPattern: f5sources:- sourceType: Metricscondition: $prefix(f5.lb)requiredDimensions: []attributes: []role: default- name: network:interfaceenabled: truedisplayName: Network interfacerules:- idPattern: network_interface_{mac.address}_{interface.name}instanceNamePattern: '{interface.name}'iconPattern: network-interfacessources:- sourceType: Metricscondition: $eq(f5.lb.sys.interface.status)attributes:- key: EnabledStatedisplayName: Enabled Statepattern: '{interface.enabled}'- key: MacAddressdisplayName: MAC Addresspattern: '{mac.address}'- key: ifOperStatusdisplayName: Operational statuspattern: '{interface.status}'role: default- idPattern: network_interface_{mac.address}_{interface.name}instanceNamePattern: '{interface.name}'iconPattern: network-interfacessources:- sourceType: Metricscondition: $prefix(f5.lb.sys.interface)requiredDimensions: []attributes: []role: defaultrelationships:- fromType: f5lb:interfacetypeOfRelation: RUNS_ONtoType: f5lb:instancesources:- sourceType: Metricscondition: $prefix(f5.lb.sys.interface)- fromType: f5lb:interfacetypeOfRelation: SAME_AStoType: network:interfacesources:- sourceType: Metricscondition: $prefix(f5.lb.sys.interface)- fromType: f5lb:instancetypeOfRelation: SAME_AStoType: network:devicesources:- sourceType: Metricscondition: $prefix(f5.lb)screens:- entityType: network:devicepropertiesCard:properties:- type: RELATIONrelation:entitySelectorTemplate: type(f5lb:instance),fromRelationships.isSameAs($(entityConditions))displayName: F5 Load balancerconditions:- relatedEntity|entitySelectorTemplate=type(f5lb:instance),fromRelationships.isSameAs($(entityConditions))- entityAttribute|devMonitoringMode=ExtensiondetailsInjections:- type: CHART_GROUPkey: f5_instance-charts-cpuentitySelectorTemplate: type(f5lb:instance),fromRelationships.isSameAs($(entityConditions))conditions:- relatedEntity|entitySelectorTemplate=type(f5lb:instance),fromRelationships.isSameAs($(entityConditions))- entityAttribute|devMonitoringMode=Extension- type: CHART_GROUPkey: network-interfaces-listchartsCards:- key: network-interfaces-listmode: NORMALtarget: BOTHdisplayName: TrafficnumberOfVisibleCharts: 1conditions:- entityAttribute|devMonitoringMode=Extensioncharts:- displayName: Traffic in/outvisualizationType: GRAPH_CHARTgraphChartConfig: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:instancedetailsSettings:staticContent:showProblems: trueshowProperties: trueshowTags: trueshowGlobalFilter: trueshowAddTag: truetarget: BOTHlayout:autoGenerate: falsecards:- key: f5_instance-charts-cputype: CHART_GROUP- key: f5_instance-charts-memorytype: CHART_GROUPchartsCards:- key: f5_instance-charts-cputarget: BOTHmode: NORMALdisplayName: CPUnumberOfVisibleCharts: 4chartsInRow: 4charts:- displayName: CPU BreakdownvisualizationType: GRAPH_CHARTgraphChartConfig:connectGaps: truestacked: truemetrics:- 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: Uservisualization:themeColor: DEFAULTseriesType: AREA- displayName: System CPUvisualizationType: GRAPH_CHARTgraphChartConfig:connectGaps: truemetrics:- 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: BLUEseriesType: LINE- displayName: User CPUvisualizationType: GRAPH_CHARTgraphChartConfig:connectGaps: truemetrics:- 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: BLUEseriesType: LINE- displayName: Idle CPUvisualizationType: GRAPH_CHARTgraphChartConfig:connectGaps: truemetrics:- 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: BLUEseriesType: LINE- key: f5_instance-charts-memorytarget: BOTHmode: NORMALdisplayName: MemorynumberOfVisibleCharts: 4hideEmptyCharts: truecharts:- displayName: Memory breakdownvisualizationType: GRAPH_CHARTgraphChartConfig:connectGaps: trueyAxes:- key: y-absoluteposition: LEFTvisible: true- key: y-relativeposition: RIGHTvisible: truemin: '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-absolutevisualization:themeColor: BLUEseriesType: AREAdisplayName: 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-absolutevisualization:themeColor: ORANGEseriesType: AREAdisplayName: Used
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.
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.
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:
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.
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 BalancersourcePath: openpipeline/metrics.source.jsonconfigScope: logspipelines:- displayName: F5 Load BalancerpipelinePath: openpipeline/metrics.pipeline.jsonconfigScope: logs
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:
sourceType must be extensionsource must be your extension's namepipelineType must be custom (can also be omitted)pipelineId can be anything (just keep it consistent between files)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 fileBefore 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.
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_DEVICEf5lb: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_INTERFACEruns_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_DEVICEsysuptime metric carries all the network device attribute fieldsif.status metric carries all the network interface attribute fieldsf5.lb.sys.interface or com.dynatrace.extension.network_device.if should also be enriched with the interface node IDWe'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).
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: f5interval:minutes: 1dimensions:- key: chassis.macvalue: this:device.chassis_mac
Update minDynatraceVersion to 1.333.0.
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\""}}
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.
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.
{"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.
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.
{"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}}]}}
Once you have packaged, signed, and uploaded your extension to your Dynatrace environment, you can verify your work.
Once your extension is configured and starts collecting data, go to
Infrastructure & Operations > Network devices to view the network devices created.
device_type field valueFor 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.
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.
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.