diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 0000000..f37d717 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: a52eb9f63932497422841caa6f1f3f43 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/changelog.doctree b/.doctrees/changelog.doctree new file mode 100644 index 0000000..27b8ba7 Binary files /dev/null and b/.doctrees/changelog.doctree differ diff --git a/.doctrees/doc/filters.doctree b/.doctrees/doc/filters.doctree new file mode 100644 index 0000000..4180e5c Binary files /dev/null and b/.doctrees/doc/filters.doctree differ diff --git a/.doctrees/doc/functions.doctree b/.doctrees/doc/functions.doctree new file mode 100644 index 0000000..b3e57fc Binary files /dev/null and b/.doctrees/doc/functions.doctree differ diff --git a/.doctrees/doc/language_reference.doctree b/.doctrees/doc/language_reference.doctree new file mode 100644 index 0000000..62de95a Binary files /dev/null and b/.doctrees/doc/language_reference.doctree differ diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle new file mode 100644 index 0000000..d573c7f Binary files /dev/null and b/.doctrees/environment.pickle differ diff --git a/.doctrees/examples/interface_states/admin_state.doctree b/.doctrees/examples/interface_states/admin_state.doctree new file mode 100644 index 0000000..3829f9e Binary files /dev/null and b/.doctrees/examples/interface_states/admin_state.doctree differ diff --git a/.doctrees/examples/interface_states/count_if_nz_out_or_in_trfk.doctree b/.doctrees/examples/interface_states/count_if_nz_out_or_in_trfk.doctree new file mode 100644 index 0000000..498445c Binary files /dev/null and b/.doctrees/examples/interface_states/count_if_nz_out_or_in_trfk.doctree differ diff --git a/.doctrees/examples/interface_states/count_if_nz_out_trfk.doctree b/.doctrees/examples/interface_states/count_if_nz_out_trfk.doctree new file mode 100644 index 0000000..2a980b2 Binary files /dev/null and b/.doctrees/examples/interface_states/count_if_nz_out_trfk.doctree differ diff --git a/.doctrees/examples/interface_states/if_info_filter_port_desc.doctree b/.doctrees/examples/interface_states/if_info_filter_port_desc.doctree new file mode 100644 index 0000000..9ab7a37 Binary files /dev/null and b/.doctrees/examples/interface_states/if_info_filter_port_desc.doctree differ diff --git a/.doctrees/examples/interface_states/index.doctree b/.doctrees/examples/interface_states/index.doctree new file mode 100644 index 0000000..3b12502 Binary files /dev/null and b/.doctrees/examples/interface_states/index.doctree differ diff --git a/.doctrees/examples/interface_states/intf_counter_rate_sum_per_dev.doctree b/.doctrees/examples/interface_states/intf_counter_rate_sum_per_dev.doctree new file mode 100644 index 0000000..94bf8ee Binary files /dev/null and b/.doctrees/examples/interface_states/intf_counter_rate_sum_per_dev.doctree differ diff --git a/.doctrees/examples/interface_states/lanz_queue_size.doctree b/.doctrees/examples/interface_states/lanz_queue_size.doctree new file mode 100644 index 0000000..7da3036 Binary files /dev/null and b/.doctrees/examples/interface_states/lanz_queue_size.doctree differ diff --git a/.doctrees/examples/interface_states/port_utilization.doctree b/.doctrees/examples/interface_states/port_utilization.doctree new file mode 100644 index 0000000..e34c30c Binary files /dev/null and b/.doctrees/examples/interface_states/port_utilization.doctree differ diff --git a/.doctrees/examples/layer2_and_layer3/arp_entries.doctree b/.doctrees/examples/layer2_and_layer3/arp_entries.doctree new file mode 100644 index 0000000..2eb3be5 Binary files /dev/null and b/.doctrees/examples/layer2_and_layer3/arp_entries.doctree differ diff --git a/.doctrees/examples/layer2_and_layer3/bgp_states.doctree b/.doctrees/examples/layer2_and_layer3/bgp_states.doctree new file mode 100644 index 0000000..7a2a573 Binary files /dev/null and b/.doctrees/examples/layer2_and_layer3/bgp_states.doctree differ diff --git a/.doctrees/examples/layer2_and_layer3/capacity_planning.doctree b/.doctrees/examples/layer2_and_layer3/capacity_planning.doctree new file mode 100644 index 0000000..503e03c Binary files /dev/null and b/.doctrees/examples/layer2_and_layer3/capacity_planning.doctree differ diff --git a/.doctrees/examples/layer2_and_layer3/igmp_snooping.doctree b/.doctrees/examples/layer2_and_layer3/igmp_snooping.doctree new file mode 100644 index 0000000..62b0569 Binary files /dev/null and b/.doctrees/examples/layer2_and_layer3/igmp_snooping.doctree differ diff --git a/.doctrees/examples/layer2_and_layer3/index.doctree b/.doctrees/examples/layer2_and_layer3/index.doctree new file mode 100644 index 0000000..73604e7 Binary files /dev/null and b/.doctrees/examples/layer2_and_layer3/index.doctree differ diff --git a/.doctrees/examples/layer2_and_layer3/mac_entries.doctree b/.doctrees/examples/layer2_and_layer3/mac_entries.doctree new file mode 100644 index 0000000..e82f785 Binary files /dev/null and b/.doctrees/examples/layer2_and_layer3/mac_entries.doctree differ diff --git a/.doctrees/examples/layer2_and_layer3/macs_per_device.doctree b/.doctrees/examples/layer2_and_layer3/macs_per_device.doctree new file mode 100644 index 0000000..aaa5b86 Binary files /dev/null and b/.doctrees/examples/layer2_and_layer3/macs_per_device.doctree differ diff --git a/.doctrees/examples/layer2_and_layer3/vrfs_in_vlans.doctree b/.doctrees/examples/layer2_and_layer3/vrfs_in_vlans.doctree new file mode 100644 index 0000000..ca1eeed Binary files /dev/null and b/.doctrees/examples/layer2_and_layer3/vrfs_in_vlans.doctree differ diff --git a/.doctrees/examples/layer2_and_layer3/webinar03_bgp.doctree b/.doctrees/examples/layer2_and_layer3/webinar03_bgp.doctree new file mode 100644 index 0000000..64cc4f2 Binary files /dev/null and b/.doctrees/examples/layer2_and_layer3/webinar03_bgp.doctree differ diff --git a/.doctrees/examples/system_health/abootfn44.doctree b/.doctrees/examples/system_health/abootfn44.doctree new file mode 100644 index 0000000..9c246fb Binary files /dev/null and b/.doctrees/examples/system_health/abootfn44.doctree differ diff --git a/.doctrees/examples/system_health/eol_planning.doctree b/.doctrees/examples/system_health/eol_planning.doctree new file mode 100644 index 0000000..3d20698 Binary files /dev/null and b/.doctrees/examples/system_health/eol_planning.doctree differ diff --git a/.doctrees/examples/system_health/fn72.doctree b/.doctrees/examples/system_health/fn72.doctree new file mode 100644 index 0000000..9d5c760 Binary files /dev/null and b/.doctrees/examples/system_health/fn72.doctree differ diff --git a/.doctrees/examples/system_health/hardware_health_check.doctree b/.doctrees/examples/system_health/hardware_health_check.doctree new file mode 100644 index 0000000..9f36474 Binary files /dev/null and b/.doctrees/examples/system_health/hardware_health_check.doctree differ diff --git a/.doctrees/examples/system_health/important_pod_count.doctree b/.doctrees/examples/system_health/important_pod_count.doctree new file mode 100644 index 0000000..d6c5637 Binary files /dev/null and b/.doctrees/examples/system_health/important_pod_count.doctree differ diff --git a/.doctrees/examples/system_health/index.doctree b/.doctrees/examples/system_health/index.doctree new file mode 100644 index 0000000..1de42e0 Binary files /dev/null and b/.doctrees/examples/system_health/index.doctree differ diff --git a/.doctrees/examples/system_health/ntp_stats.doctree b/.doctrees/examples/system_health/ntp_stats.doctree new file mode 100644 index 0000000..67529ef Binary files /dev/null and b/.doctrees/examples/system_health/ntp_stats.doctree differ diff --git a/.doctrees/examples/system_health/output_power_over_48h.doctree b/.doctrees/examples/system_health/output_power_over_48h.doctree new file mode 100644 index 0000000..9053cbb Binary files /dev/null and b/.doctrees/examples/system_health/output_power_over_48h.doctree differ diff --git a/.doctrees/examples/system_health/system_health_check.doctree b/.doctrees/examples/system_health/system_health_check.doctree new file mode 100644 index 0000000..2078d4a Binary files /dev/null and b/.doctrees/examples/system_health/system_health_check.doctree differ diff --git a/.doctrees/examples/system_health/tcam_capacity.doctree b/.doctrees/examples/system_health/tcam_capacity.doctree new file mode 100644 index 0000000..db60ae3 Binary files /dev/null and b/.doctrees/examples/system_health/tcam_capacity.doctree differ diff --git a/.doctrees/examples/system_health/varcore.doctree b/.doctrees/examples/system_health/varcore.doctree new file mode 100644 index 0000000..6538204 Binary files /dev/null and b/.doctrees/examples/system_health/varcore.doctree differ diff --git a/.doctrees/examples/system_health/xcvr_sn_list.doctree b/.doctrees/examples/system_health/xcvr_sn_list.doctree new file mode 100644 index 0000000..839c53f Binary files /dev/null and b/.doctrees/examples/system_health/xcvr_sn_list.doctree differ diff --git a/.doctrees/examples/system_health/xcvr_sn_list_filtered.doctree b/.doctrees/examples/system_health/xcvr_sn_list_filtered.doctree new file mode 100644 index 0000000..f55a098 Binary files /dev/null and b/.doctrees/examples/system_health/xcvr_sn_list_filtered.doctree differ diff --git a/.doctrees/index.doctree b/.doctrees/index.doctree new file mode 100644 index 0000000..d490b4c Binary files /dev/null and b/.doctrees/index.doctree differ diff --git a/.doctrees/index_doc.doctree b/.doctrees/index_doc.doctree new file mode 100644 index 0000000..3127861 Binary files /dev/null and b/.doctrees/index_doc.doctree differ diff --git a/.doctrees/index_examples.doctree b/.doctrees/index_examples.doctree new file mode 100644 index 0000000..584d1db Binary files /dev/null and b/.doctrees/index_examples.doctree differ diff --git a/.doctrees/index_stdlib.doctree b/.doctrees/index_stdlib.doctree new file mode 100644 index 0000000..afaa539 Binary files /dev/null and b/.doctrees/index_stdlib.doctree differ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..87c1df3 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +aql.arista.com \ No newline at end of file diff --git a/_downloads/0748437a28de8fd92c5568ce35a5c8e0/macs_per_device.json b/_downloads/0748437a28de8fd92c5568ce35a5c8e0/macs_per_device.json new file mode 100644 index 0000000..919c793 --- /dev/null +++ b/_downloads/0748437a28de8fd92c5568ce35a5c8e0/macs_per_device.json @@ -0,0 +1,63 @@ +{ + "dashboards": [ + { + "key": "fdb63033-da24-49ed-b7d0-64e0c5aba7ab", + "createdAt": [ + 62457345, + 1561 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "MACs learnt per device per interface", + "description": "", + "widgets": [ + { + "id": "341effe6-9b64-4d0f-9ddb-7b5f25d567ad", + "name": "MACs", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 5, + "height": 15 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let data = merge(`<_device>:/Smash/bridging/status/smashFdbStatus`)\nlet numberMAC = newDict()\n\n\nfor deviceKey, deviceValue in data {\n if dictHasKey(numberMAC, data[deviceKey][\"intf\"]) {\n numberMAC[data[deviceKey][\"intf\"]][\"MACs\"] = numberMAC[data[deviceKey][\"intf\"]][\"MACs\"] + 1\n } else {\n numberMAC[data[deviceKey][\"intf\"]] = newDict()\n numberMAC[data[deviceKey][\"intf\"]][\"MACs\"] = 1\n }\n}\n\n\nnumberMAC\n", + "visualization": "table" + }, + "location": "main" + }, + { + "id": "cb4822fb-3e3e-4a41-85ef-3ab4c5395f83", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "SSJ17371234", + "inputName": "device", + "inputSource": "devices", + "inputWidgetId": "cb4822fb-3e3e-4a41-85ef-3ab4c5395f83", + "tagLabel": "device" + }, + "location": "inputs" + } + ], + "lastUpdated": 1676173561773, + "lastUpdatedBy": "tamas" + } + ] + } \ No newline at end of file diff --git a/_downloads/0be014aa4bf423779e49bb86ddd39c80/arp_entries.json b/_downloads/0be014aa4bf423779e49bb86ddd39c80/arp_entries.json new file mode 100644 index 0000000..7bdb1b7 --- /dev/null +++ b/_downloads/0be014aa4bf423779e49bb86ddd39c80/arp_entries.json @@ -0,0 +1,42 @@ +{ + "dashboards": [ + { + "key": "ab46a6ac-57e8-4321-ae2e-16468ede5902", + "createdAt": [ + 60535775, + 1561 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Number of ARP entries across all devices", + "description": "", + "widgets": [ + { + "id": "d52c1fda-efa1-481d-9404-8e1eb08d99d5", + "name": "Number of ARP entries across all devices", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 5, + "height": 7 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let data = `*:/Smash/arp/status/_counts` \nsum(data | map(merge(_value)) | where(dictHasKey(_value, \"arpEntry\")) | map(_value[\"arpEntry\"]))\n", + "visualization": "singleValue" + }, + "location": "main" + } + ], + "lastUpdated": 1676171600753, + "lastUpdatedBy": "tamas" + } + ] + } \ No newline at end of file diff --git a/_downloads/0d01cfc20c7c8f9a4a0692994c297513/mac_entries.json b/_downloads/0d01cfc20c7c8f9a4a0692994c297513/mac_entries.json new file mode 100644 index 0000000..6bb4d2b --- /dev/null +++ b/_downloads/0d01cfc20c7c8f9a4a0692994c297513/mac_entries.json @@ -0,0 +1,42 @@ +{ + "dashboards": [ + { + "key": "7ee0a4c8-8e58-4763-95b4-464332fabb82", + "createdAt": [ + 62254169, + 1561 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Number of MAC entriess across all devices", + "description": "", + "widgets": [ + { + "id": "e44b876d-1469-4fb7-828e-78cfb3bb294b", + "name": "MACs", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 5, + "height": 8 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let data = `*:/Smash/bridging/status/_counts`\nsum(data | map(merge(_value)) | where(dictHasKey(_value, \"smashFdbStatus\")) | map(_value[\"smashFdbStatus\"]))\n", + "visualization": "singleValue" + }, + "location": "main" + } + ], + "lastUpdated": 1676173282858, + "lastUpdatedBy": "tamas" + } + ] + } \ No newline at end of file diff --git a/_downloads/1543346349d3b8d124783df50765260a/bgp_states.json b/_downloads/1543346349d3b8d124783df50765260a/bgp_states.json new file mode 100644 index 0000000..6bd34ca --- /dev/null +++ b/_downloads/1543346349d3b8d124783df50765260a/bgp_states.json @@ -0,0 +1,182 @@ +{ + "dashboards": [ + { + "key": "969b5f6d-4765-4a75-999f-8e2cce796c65", + "createdAt": [ + 1031714660, + 1574 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "BGP States", + "description": "", + "widgets": [ + { + "id": "0a782e66-197b-4453-9d09-e056fb361a4a", + "name": "BGP Session Details in the Default VRF for all Devices", + "position": { + "x": 5, + "y": 0 + }, + "dimensions": { + "width": 12, + "height": 11 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\n\nif str(_POD_NAME) == \"\" {\n let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`\n} else {\n let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\n}\n\n# This is the table\nlet res = newDict()\nlet id = 0\n# Lets loop over every device\nfor device, deviceSessions in bgpNeighbors{\n # And each session on the devices\n for ip, sessionData in deviceSessions{\n let data = merge(sessionData)\n # Add one to the ID\n let id = id + 1\n res[id] = newDict()\n # This is where we add the various columns\n res[id][\"0. Device\"] = device\n res[id][\"1. Status\"] = data[\"bgpState\"][\"Name\"]\n res[id][\"2. Peering Address\"] = data[\"bgpPeerLocalAddr\"]\n res[id][\"3. Neighbor Address\"] = data[\"key\"]\n res[id][\"4. Neighbor AS\"] = data[\"bgpPeerAs\"][\"value\"]\n }\n}\nres\n", + "graphConfig": { + "columns": { + "0. Device": { + "mapToHostname": true + }, + "1. Status": { + "colorMappings": [ + { + "type": "value", + "options": { + "Established": { + "color": "green", + "index": 0 + }, + "Active": { + "color": "yellow", + "index": 1 + }, + "Idle": { + "color": "orange", + "index": 2 + }, + "OpenSent": { + "color": "purple", + "index": 3 + }, + "Connect": { + "color": "red9", + "index": 4 + } + } + } + ] + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "a5c2e69b-5562-4fa6-9e9c-8f23169c14bf", + "name": "BGP Session Status", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 5, + "height": 11 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let neighbors = `analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`\n\n# Dict to store the states and counts\nlet res = newDict()\n# Loop over each device\nfor device, deviceSessions in neighbors{\n # Loop over each session on each device\n for ip, sessionData in deviceSessions{\n let data = merge(sessionData)\n # Have we used this status yet?\n let status = data[\"bgpState\"][\"Name\"]\n if !dictHasKey(res, status) {\n # If not lets set it use count to zero\n res[status] = 0\n }\n # Add one to the total times this status is used\n res[status] = res[status] + 1\n }\n}\nres\n", + "graphConfig": { + "colorOverrides": { + "Idle": "orange", + "OpenSent": "purple", + "Established": "green", + "Active": "yellow", + "Connect": "red9" + }, + "mapToHostname": true, + "unit": "sessions" + }, + "visualization": "donutGraph" + }, + "location": "main", + "parent": "" + }, + { + "id": "29ae71af-0dfb-41e8-948d-2dda5abd32e3", + "name": "BGP Sessions that are Not Established", + "position": { + "x": 0, + "y": 11 + }, + "dimensions": { + "width": 17, + "height": 8 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\r\n\r\nif str(_POD_NAME) == \"\" {\r\n let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`\r\n} else {\r\n let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n}\r\n\r\n# This is the table\r\nlet res = newDict()\r\nlet id = 0\r\n# Lets loop over every device\r\nfor device, deviceSessions in bgpNeighbors{\r\n # And each session on the devices\r\n for ip, sessionData in deviceSessions{\r\n let data = merge(sessionData)\r\n # Add one to the ID\r\n let id = id + 1\r\n res[id] = newDict()\r\n # This is where we add the various columns\r\n res[id][\"0. Device\"] = device\r\n res[id][\"1. Status\"] = data[\"bgpState\"][\"Name\"]\r\n res[id][\"2. Peering Address\"] = data[\"bgpPeerLocalAddr\"]\r\n res[id][\"3. Neighbor Address\"] = data[\"key\"]\r\n res[id][\"4. Neighbor AS\"] = data[\"bgpPeerAs\"][\"value\"]\r\n }\r\n}\r\n\r\nres | where(_value[\"1. Status\"] != \"Established\")\r\n", + "graphConfig": { + "columns": { + "0. Device": { + "mapToHostname": true + }, + "1. Status": { + "colorMappings": [ + { + "type": "value", + "options": { + "Active": { + "color": "yellow", + "index": 0 + }, + "Connect": { + "color": "red9", + "index": 1 + }, + "Idle": { + "color": "orange", + "index": 2 + }, + "OpenSent": { + "color": "purple", + "index": 3 + } + } + } + ] + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "b011dce3-f0ff-4bb1-b2bd-6ee24d256bcc", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "", + "inputName": "POD_NAME", + "inputSource": "devices", + "inputWidgetId": "9d02c221-74ff-47dd-9b79-5ef575246c91", + "tagLabel": "pod_name" + }, + "location": "inputs", + "parent": "" + } + ], + "lastUpdated": 1691101487763, + "lastUpdatedBy": "tamas" + } + ] +} \ No newline at end of file diff --git a/_downloads/2946232ea3da42fd32d1958cdd355eb3/xcvr_sn_list_filtered.json b/_downloads/2946232ea3da42fd32d1958cdd355eb3/xcvr_sn_list_filtered.json new file mode 100644 index 0000000..9e98462 --- /dev/null +++ b/_downloads/2946232ea3da42fd32d1958cdd355eb3/xcvr_sn_list_filtered.json @@ -0,0 +1,71 @@ +{ + "dashboards": [ + { + "key": "93b5abba-7807-453a-be77-56a1fde055fd", + "createdAt": [ + 977361740, + 1579 + ], + "createdBy": "cvpadmin", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Filtered Transcevier Data", + "description": "", + "widgets": [ + { + "id": "0c08fed5-cd5a-44be-b083-ab950e8c93e8", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 10, + "height": 14 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let data = `*:/Sysdb/hardware/archer/xcvr/status/all/*`\nlet xcvrStatus = data | map(_value | mapne(_value, _value | field(\"goVendorInfo\") | field(\"vendorSn\")))\nlet xcvrStatus = xcvrStatus | map(_value | mapne(_value[0], _value))\n\nlet filteredXcvrStat = newDict()\n\nfor deviceKey, deviceValue in xcvrStatus {\n filteredXcvrStat[deviceKey] = newDict()\n for interfaceKey, interfaceValue in deviceValue {\n if reMatch(interfaceValue, _regexInput){\n filteredXcvrStat[deviceKey][interfaceKey] = interfaceValue\n\n }\n }\n if length(filteredXcvrStat[deviceKey]) == 0 {\n dictRemove(filteredXcvrStat, deviceKey)\n }\n}\n\nfilteredXcvrStat\n", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "85920a17-dd18-47b0-a315-ba350f6f0a6d", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 6, + "height": 2 + }, + "type": "variable-widget", + "inputs": { + "defaultValue": "", + "inputName": "regexInput", + "inputType": "FreeForm", + "variableType": "String" + }, + "location": "inputs", + "parent": "" + } + ], + "lastUpdated": 1696513262320, + "lastUpdatedBy": "cvpadmin" + } + ] +} \ No newline at end of file diff --git a/_downloads/430ffc3827e9c209b9122a4036a9b9bd/xcvr_sn_list.json b/_downloads/430ffc3827e9c209b9122a4036a9b9bd/xcvr_sn_list.json new file mode 100644 index 0000000..93c3030 --- /dev/null +++ b/_downloads/430ffc3827e9c209b9122a4036a9b9bd/xcvr_sn_list.json @@ -0,0 +1,57 @@ +{ + "dashboards": [ + { + "key": "eb005f37-e4cb-41f9-893b-18b4e8fff7cb", + "createdAt": [ + 500143833, + 1563 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Xcvr inventory", + "description": "", + "widgets": [ + { + "id": "abe98a7a-57cc-468b-95c6-f16eecb0ad78", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 24, + "height": 26 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let xcvrStatus = `*:/Sysdb/hardware/archer/xcvr/status/all/*`\nlet filteredXcvrStat = xcvrStatus | map(_value | mapne(_value, _value | field(\"goVendorInfo\") | field(\"vendorSn\")))\nfilteredXcvrStat | map(_value | mapne(_value, _value[0]))\n", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true, + "columnTitle": "Hostname" + } + } + }, + "visualization": "table" + }, + "location": "main", + "styles": { + "hideTitle": true, + "backgroundColor": "", + "hideHorizontalBar": false, + "titleSize": 14 + }, + "parent": "" + } + ], + "lastUpdated": 1691168645813, + "lastUpdatedBy": "tamas" + } + ] +} \ No newline at end of file diff --git a/_downloads/46281ae76b1bcd2f68154b533d4dc2fc/tcam_capacity.json b/_downloads/46281ae76b1bcd2f68154b533d4dc2fc/tcam_capacity.json new file mode 100644 index 0000000..e0236f8 --- /dev/null +++ b/_downloads/46281ae76b1bcd2f68154b533d4dc2fc/tcam_capacity.json @@ -0,0 +1,733 @@ +{ + "dashboards": [ + { + "key": "7c052c12-58de-4027-989c-9c2afb1adb77", + "createdAt": [ + 22352252, + 1575 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "TCAM Capacity", + "description": "", + "widgets": [ + { + "id": "29b2c81c-bf65-4a58-95c9-b8cd971e068a", + "name": "7280R2 Switches", + "position": { + "x": 0, + "y": 6 + }, + "dimensions": { + "width": 12, + "height": 7 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/switch_role/value/egw/elements`)\r\nlet hwCapL3 =`*:/Sysdb/hardware/capacity/status/l3/entry`\r\n\r\nlet podDeviceList = merge(`analytics:/tags/labels/devices/pod_name/value/<_pod_name>/elements`)\r\n\r\nif str(_pod_name) == \"\" {\r\n let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(merge(_value) )\r\n} else {\r\n let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(podDeviceList, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(merge(_value) )\r\n}\r\n\r\nfor deviceKey, deviceValue in filteredHwCapL3{\r\n filteredHwCapL3[deviceKey][\"MAC\"] = filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"LEM\\\", \\\"feature\\\": \\\"MAC\\\"}\")][\"used\"] / filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"LEM\\\", \\\"feature\\\": \\\"MAC\\\"}\")][\"maxLimit\"]*100\r\n filteredHwCapL3[deviceKey][\"FEC Routing\"] = filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"FEC\\\", \\\"feature\\\": \\\"Routing\\\"}\")][\"used\"] / filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"FEC\\\", \\\"feature\\\": \\\"Routing\\\"}\")][\"maxLimit\"]*100\r\n filteredHwCapL3[deviceKey][\"Routing1\"] = filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho\\\", \\\"table\\\":\\\"Routing\\\", \\\"feature\\\": \\\"Resource1\\\"}\")][\"used\"] / filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho\\\", \\\"table\\\":\\\"Routing\\\", \\\"feature\\\": \\\"Resource1\\\"}\")][\"maxLimit\"]*100\r\n filteredHwCapL3[deviceKey][\"Routing2\"] = filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho\\\", \\\"table\\\":\\\"Routing\\\", \\\"feature\\\": \\\"Resource2\\\"}\")][\"used\"] / filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho\\\", \\\"table\\\":\\\"Routing\\\", \\\"feature\\\": \\\"Resource2\\\"}\")][\"maxLimit\"]*100\r\n filteredHwCapL3[deviceKey][\"Routing3\"] = filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho\\\", \\\"table\\\":\\\"Routing\\\", \\\"feature\\\": \\\"Resource3\\\"}\")][\"used\"] / filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho\\\", \\\"table\\\":\\\"Routing\\\", \\\"feature\\\": \\\"Resource3\\\"}\")][\"maxLimit\"]*100\r\n}\r\n\r\nfilteredHwCapL3 | map(_value | fields(\"MAC\",\"FEC Routing\", \"Routing1\", \"Routing2\", \"Routing3\"))", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true, + "columnTitle": "Device" + }, + "Routing3": { + "type": "number", + "decimals": 2, + "unit": "%", + "showDotIndicator": false, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "FEC Routing": { + "type": "number", + "decimals": 2, + "unit": "%", + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "MAC": { + "unit": "%", + "type": "number", + "decimals": 2, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "Routing1": { + "unit": "%", + "type": "number", + "decimals": 2, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "Routing2": { + "unit": "%", + "type": "number", + "decimals": 2, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "04b7773a-88cd-42d0-bb31-949d5904fca4", + "name": "7050X3 Switches", + "position": { + "x": 0, + "y": 13 + }, + "dimensions": { + "width": 12, + "height": 7 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "# Get the devices that have the `hdl` (high-density leaf) tag\r\nlet devices = merge(`analytics:/tags/labels/devices/switch_role/value/hdl/elements`)\r\n\r\n# Get the L3 hardware capacity for all devices\r\nlet hwCapL3 =`*:/Sysdb/hardware/capacity/status/l3/entry`\r\n\r\n# Get the L2 hardware capacity for all devices\r\nlet hwCapL2 =`*:/Sysdb/hardware/capacity/status/l2/entry`\r\n\r\n# Get the devices that are part of a specific pod\r\nlet podDeviceList = merge(`analytics:/tags/labels/devices/pod_name/value/<_pod_name>/elements`)\r\n\r\n# If the POD_NAME variable is not set (none) show the utilization for all pods\r\nif str(_pod_name) == \"\" {\r\n let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(merge(_value) )\r\n let filteredHwCapL2 = hwCapL2 | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(merge(_value) )\r\n} else {\r\n let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(podDeviceList, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(merge(_value) )\r\n let filteredHwCapL2 = hwCapL2 | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(podDeviceList, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(merge(_value) )\r\n}\r\n\r\n# Build a new table with MAC, HOST, LPM and NextHop utilization\r\nfor deviceKey, deviceValue in filteredHwCapL3{\r\n filteredHwCapL3[deviceKey][\"Host Percent\"] = filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"Host\\\", \\\"feature\\\": \\\"\\\"}\")][\"used\"] / filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"Host\\\", \\\"feature\\\": \\\"\\\"}\")][\"maxLimit\"]*100\r\n filteredHwCapL3[deviceKey][\"LPM Percent\"] = filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"LPM\\\", \\\"feature\\\": \\\"\\\"}\")][\"used\"] / filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"LPM\\\", \\\"feature\\\": \\\"\\\"}\")][\"maxLimit\"]*100\r\n filteredHwCapL3[deviceKey][\"NextHop Percent\"] = filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"NextHop\\\", \\\"feature\\\": \\\"\\\"}\")][\"used\"] / filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"NextHop\\\", \\\"feature\\\": \\\"\\\"}\")][\"maxLimit\"]*100\r\n filteredHwCapL3[deviceKey][\"MAC Percent\"] = filteredHwCapL2[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Linecard0/0\\\", \\\"table\\\":\\\"MAC\\\", \\\"feature\\\": \\\"L2\\\"}\")][\"used\"] / filteredHwCapL2[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Linecard0/0\\\", \\\"table\\\":\\\"MAC\\\", \\\"feature\\\": \\\"L2\\\"}\")][\"maxLimit\"]*100\r\n}\r\nfilteredHwCapL3 | map(_value | fields(\"MAC Percent\",\"Host Percent\", \"LPM Percent\", \"NextHop Percent\"))", + "graphConfig": { + "columns": { + "key": { + "columnTitle": "Device", + "mapToHostname": true + }, + "MAC Percent": { + "unit": "%", + "type": "number", + "decimals": 2, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "LPM Percent": { + "unit": "%", + "type": "number", + "decimals": 2, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "Host Percent": { + "unit": "%", + "type": "number", + "decimals": 2, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "NextHop Percent": { + "unit": "%", + "type": "number", + "decimals": 2, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "9489f737-3a97-4c27-afb2-cdd9f96f94ae", + "name": "7020R Switches", + "position": { + "x": 0, + "y": 20 + }, + "dimensions": { + "width": 12, + "height": 7 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "# Get the devices that have the `ldl` (low-density leaf) tag\r\nlet devices = merge(`analytics:/tags/labels/devices/switch_role/value/ldl/elements`)\r\n\r\n# Get the L3 hardware capacity for all devices\r\nlet hwCapL3 =`*:/Sysdb/hardware/capacity/status/l3/entry`\r\n\r\n# Get the multicast hardware capacity for all devices\r\nlet hwCapMcast = `*:/Sysdb/hardware/capacity/status/mcast/entry`\r\n\r\n# Get the devices that are part of a specific pod\r\nlet podDeviceList = merge(`analytics:/tags/labels/devices/pod_name/value/<_pod_name>/elements`)\r\nlet filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(podDeviceList, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))| map(merge(_value) )\r\nlet filteredHwCapMcast = hwCapMcast | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(podDeviceList, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(merge(_value) )\r\n\r\n# Build a new table with MAC, MCDB, FEC Routing, Routing1, Routing2 and Routing3 utilization\r\nfor deviceKey, deviceValue in filteredHwCapL3{\r\n filteredHwCapL3[deviceKey][\"MAC\"] = filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"LEM\\\", \\\"feature\\\": \\\"MAC\\\"}\")][\"used\"] / filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"LEM\\\", \\\"feature\\\": \\\"MAC\\\"}\")][\"maxLimit\"]*100\r\n filteredHwCapL3[deviceKey][\"FEC Routing\"] = filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"FEC\\\", \\\"feature\\\": \\\"Routing\\\"}\")][\"used\"] / filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"\\\", \\\"table\\\":\\\"FEC\\\", \\\"feature\\\": \\\"Routing\\\"}\")][\"maxLimit\"]*100\r\n filteredHwCapL3[deviceKey][\"Routing1\"] = filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho\\\", \\\"table\\\":\\\"Routing\\\", \\\"feature\\\": \\\"Resource1\\\"}\")][\"used\"] / filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho\\\", \\\"table\\\":\\\"Routing\\\", \\\"feature\\\": \\\"Resource1\\\"}\")][\"maxLimit\"]*100\r\n filteredHwCapL3[deviceKey][\"Routing2\"] = filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho\\\", \\\"table\\\":\\\"Routing\\\", \\\"feature\\\": \\\"Resource2\\\"}\")][\"used\"] / filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho\\\", \\\"table\\\":\\\"Routing\\\", \\\"feature\\\": \\\"Resource2\\\"}\")][\"maxLimit\"]*100\r\n filteredHwCapL3[deviceKey][\"Routing3\"] = filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho\\\", \\\"table\\\":\\\"Routing\\\", \\\"feature\\\": \\\"Resource3\\\"}\")][\"used\"] / filteredHwCapL3[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho\\\", \\\"table\\\":\\\"Routing\\\", \\\"feature\\\": \\\"Resource3\\\"}\")][\"maxLimit\"]*100\r\n filteredHwCapL3[deviceKey][\"MCDB\"] = filteredHwCapMcast[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho0\\\", \\\"table\\\":\\\"MCDB\\\", \\\"feature\\\": \\\"\\\"}\")][\"used\"] / filteredHwCapMcast[deviceKey][complexKey(\"{\\\"chip\\\": \\\"Jericho0\\\", \\\"table\\\":\\\"MCDB\\\", \\\"feature\\\": \\\"\\\"}\")][\"maxLimit\"]*100\r\n}\r\nfilteredHwCapL3 | map(_value | fields(\"MAC\",\"MCDB\",\"FEC Routing\", \"Routing1\", \"Routing2\", \"Routing3\"))\r\n", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true, + "columnTitle": "Device" + }, + "FEC Routing": { + "type": "number", + "decimals": 2, + "unit": "%", + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "MAC": { + "type": "number", + "decimals": 2, + "unit": "%", + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "Routing1": { + "unit": "%", + "type": "number", + "decimals": 2, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "Routing2": { + "type": "number", + "unit": "%", + "decimals": 2, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "Routing3": { + "unit": "%", + "type": "number", + "decimals": 2, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "MCDB": { + "mapToHostname": false, + "type": "number", + "decimals": 2, + "unit": "%", + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 70, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 70, + "to": 90, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 90, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "c5bc913d-3c19-4cb7-b0d1-dd79eee8fb60", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 12, + "height": 6 + }, + "type": "text-widget", + "inputs": { + "textContent": "TCAM Capacity\n\nThe 3 tables display the utilization of some TCAM specific slices.\nIn these examples pod_name and switch_role tags are used.\n\nSelect the pod. If none is selected it will show the utilization for all pods.\nAs for the switch roles, for the first table the tag switch_role:egw is used, for the the second switch_role:hdl (high density leaf) and for the last one switch_role:ldl (low density leaf)." + }, + "location": "main", + "parent": "" + }, + { + "id": "f74f224c-d2ea-4c5d-85d6-08f7a0e2f6cf", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "", + "inputName": "pod_name", + "inputSource": "devices", + "selectedCustomTags": [], + "tagLabel": "pod_name", + "tags": "" + }, + "location": "inputs", + "parent": "" + } + ], + "lastUpdated": 1691168302435, + "lastUpdatedBy": "tamas" + } + ] +} \ No newline at end of file diff --git a/_downloads/4def4e6e3be120dc4508650622c1e068/output_power_over_48h.json b/_downloads/4def4e6e3be120dc4508650622c1e068/output_power_over_48h.json new file mode 100644 index 0000000..c8b9b74 --- /dev/null +++ b/_downloads/4def4e6e3be120dc4508650622c1e068/output_power_over_48h.json @@ -0,0 +1,66 @@ +{ + "dashboards": [ + { + "key": "020f88a1-8f36-4ea4-984b-0e6e04cf0c49", + "createdAt": [ + 66742831, + 1558 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Output power consumption over 48h", + "description": "", + "widgets": [ + { + "id": "e280cf43-878c-4e51-a2dd-3f18a8333ead", + "name": "Total Output Power Per Device", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 9, + "height": 11 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let powerSupply = `analytics:/Devices/*/versioned-data/environment/power/aggregate/*/out/15m`[48h]\nlet result = newDict()\nfor key, value in _device{\n \n if dictHasKey(powerSupply,key) { \n for psk,psv in powerSupply[key]{\n result[key+\"-\"+psk] = powerSupply[key][psk] | field(\"value\") | field(\"avg\")\n }\n } \n}\nresult", + "graphConfig": { + "mapToHostname": true, + "unit": "W" + }, + "visualization": "horizonGraph" + }, + "location": "main" + }, + { + "id": "4cc83150-9bab-442a-a3a3-18fa0d57caac", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 9, + "height": 2 + }, + "type": "tag-query-widget", + "inputs": { + "inputName": "device", + "inputSource": "devices", + "inputWidgetId": "4cc83150-9bab-442a-a3a3-18fa0d57caac", + "defaultValue": "device:*" + }, + "location": "inputs" + } + ], + "lastUpdated": 1678762440534, + "lastUpdatedBy": "tamas" + } + ] + } \ No newline at end of file diff --git a/_downloads/52257ff7a9e1e11d2c327bdbbcd8a11a/hardware_health_check.json b/_downloads/52257ff7a9e1e11d2c327bdbbcd8a11a/hardware_health_check.json new file mode 100644 index 0000000..a1e53e5 --- /dev/null +++ b/_downloads/52257ff7a9e1e11d2c327bdbbcd8a11a/hardware_health_check.json @@ -0,0 +1,327 @@ +{ + "dashboards": [ + { + "key": "50865968-9023-497b-b710-bf3d2bb7b294", + "createdAt": [ + 25589288, + 1575 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Hardware Health Check", + "description": "", + "widgets": [ + { + "id": "abeec35c-30f7-40e8-be14-c5a1acbfebba", + "name": "Power Supply Status per Device", + "position": { + "x": 0, + "y": 9 + }, + "dimensions": { + "width": 12, + "height": 21 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\r\nlet devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/<_SWITCH_ROLE>/elements`)\r\n\r\n\r\nlet powerSupply = `*:/Sysdb/environment/archer/power/status/powerSupply/*`\r\nlet filteredPsuStats = powerSupply | where(dictHasKey(powerSupply, _key) == true) | recmap(2, (merge(_value)[\"state\"][\"Name\"]))\r\n\r\nif str(_POD_NAME) == \"\" && str(_SWITCH_ROLE) == \"\" {\r\n let result = filteredPsuStats\r\n} else {\r\n if str(_SWITCH_ROLE) == \"\" {\r\n let result = filteredPsuStats | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n } else {\r\n if str(_POD_NAME) == \"\" {\r\n let result = filteredPsuStats | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n } else {\r\n let result = filteredPsuStats | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n }\r\n }\r\n}\r\n\r\nfor deviceKey, deviceValue in result{\r\n for powerSupplyKey, powerSupplyValue in deviceValue{\r\n if powerSupplyValue==\"ok\"{\r\n result[deviceKey][powerSupplyKey]=\"✔️\"\r\n } else{\r\n result[deviceKey][powerSupplyKey]=\"❌\"\r\n }\r\n }\r\n}\r\nresult", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true, + "columnTitle": "Device" + }, + "PowerSupply1": { + "showDotIndicator": false, + "colorMappings": [] + }, + "PowerSupply2": { + "showDotIndicator": false, + "colorMappings": [] + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "4eafc49f-18c1-44a3-8ac0-6ce698e47f12", + "name": "Power Supplies in the Fabric", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 9 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let powerSupplies = `*:/Sysdb/environment/archer/power/status/powerSupply/*`\r\nlet data = powerSupplies | where(dictHasKey(powerSupplies, _key) == true) | recmap(2, (merge(_value)[\"state\"][\"Name\"]))\r\n\r\nlet loss = 0\r\nlet ok = 0\r\nfor deviceKey, deviceValue in data{\r\n for powerSuppliesKey, powerSuppliesValue in deviceValue{\r\n if powerSuppliesValue==\"ok\"{\r\n let ok = ok+1\r\n } else{\r\n let loss = loss+1\r\n }\r\n }\r\n}\r\n\r\nlet usageDict = newDict()\r\n\r\nusageDict[\"PowerSupplies OK\"] = ok\r\nusageDict[\"PowerSupplies NOK\"] = loss\r\n \r\nusageDict", + "graphConfig": { + "colorOverrides": { + "PowerSupplies OK": "green", + "PowerSupplies NOK": "red" + }, + "mapToHostname": true, + "unit": "powersupplies" + }, + "visualization": "donutGraph" + }, + "location": "main", + "parent": "" + }, + { + "id": "0a19206b-2cae-4d38-a2d6-ce6b478cc9b6", + "name": "Fan Status per Device", + "position": { + "x": 12, + "y": 0 + }, + "dimensions": { + "width": 12, + "height": 13 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\r\nlet devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/<_SWITCH_ROLE>/elements`)\r\n\r\nlet fans = `*:/Sysdb/environment/archer/cooling/status/*`\r\nlet fansHwStatus = fans | where(dictHasKey(fans, _key) == true) | recmap(2, (merge(_value)[\"hwStatus\"][\"Name\"]))\r\n\r\nif str(_POD_NAME) == \"\" && str(_SWITCH_ROLE) == \"\" {\r\n let result = fansHwStatus\r\n} else {\r\n if str(_SWITCH_ROLE) == \"\" {\r\n let result = fansHwStatus | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n } else {\r\n if str(_POD_NAME) == \"\" {\r\n let result = fansHwStatus | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n } else {\r\n let result = fansHwStatus | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n }\r\n }\r\n}\r\n\r\nfor deviceKey, deviceValue in result{\r\n for fansKey, fansValue in deviceValue{\r\n if fansValue==\"ok\"{\r\n result[deviceKey][fansKey]=\"✔️\"\r\n } else{\r\n result[deviceKey][fansKey]=\"❌\"\r\n }\r\n }\r\n}\r\nresult", + "graphConfig": { + "columns": { + "key": { + "columnTitle": "Device", + "mapToHostname": true, + "colorMappings": [ + { + "type": "value", + "options": { + "ok": { + "color": "green", + "index": 0 + } + } + } + ] + }, + "Fan1/2": { + "colorMappings": [ + { + "type": "value", + "options": { + "ok": { + "color": "green", + "index": 0 + } + } + } + ] + }, + "Fan2/2": { + "colorMappings": [ + { + "type": "value", + "options": { + "ok": { + "color": "green", + "index": 0 + } + } + } + ] + }, + "FanP1/1": { + "colorMappings": [ + { + "type": "value", + "options": { + "ok": { + "color": "green", + "index": 0 + } + } + } + ] + }, + "Fan2/1": { + "colorMappings": [ + { + "type": "value", + "options": { + "ok": { + "color": "green", + "index": 0 + } + } + } + ] + }, + "FanP2/1": { + "colorMappings": [ + { + "type": "value", + "options": { + "ok": { + "color": "green", + "index": 0 + } + } + } + ] + }, + "Fan1/1": { + "colorMappings": [ + { + "type": "value", + "options": { + "ok": { + "color": "green", + "index": 0 + } + } + } + ] + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "914738b6-ac0f-492a-8ad5-a6b5f8cac24e", + "name": "Temperature Sensors per Device", + "position": { + "x": 12, + "y": 13 + }, + "dimensions": { + "width": 12, + "height": 17 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\r\nlet devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/<_SWITCH_ROLE>/elements`)\r\n\r\nlet temperature = `*:/Sysdb/environment/archer/temperature/status/cell/1/*`\r\nlet filteredTemperatureStats = temperature | where(dictHasKey(temperature, _key) == true) | recmap(2, (merge(_value)[\"alertRaised\"]))\r\n\r\nif str(_POD_NAME) == \"\" && str(_SWITCH_ROLE) == \"\" {\r\n let result = filteredTemperatureStats\r\n} else {\r\n if str(_SWITCH_ROLE) == \"\" {\r\n let result = filteredTemperatureStats | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n } else {\r\n if str(_POD_NAME) == \"\" {\r\n let result = filteredTemperatureStats | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n } else {\r\n let result = filteredTemperatureStats | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n }\r\n }\r\n}\r\n\r\nfor deviceKey, deviceValue in result{\r\n for sensorKey, sensorValue in deviceValue{\r\n if sensorValue==false{\r\n result[deviceKey][sensorKey]=\"✔️\"\r\n } else{\r\n result[deviceKey][sensorKey]=\"❌\"\r\n }\r\n }\r\n}\r\nresult", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true, + "columnTitle": "Device" + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "8b30437c-4f2b-49d2-b01d-20eb196ecd9a", + "name": "Fans in the Fabric", + "position": { + "x": 4, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 9 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let fans = `*:/Sysdb/environment/archer/cooling/status/*`\r\nlet data = fans | where(dictHasKey(fans, _key) == true) | recmap(2, (merge(_value)[\"hwStatus\"][\"Name\"]))\r\n\r\nlet loss = 0\r\nlet ok = 0\r\n\r\nfor deviceKey, deviceValue in data{\r\n for fansKey, fansValue in deviceValue{\r\n if fansValue==\"ok\"{\r\n let ok = ok +1\r\n } else{\r\n let loss = loss +1\r\n }\r\n }\r\n}\r\n\r\nlet usageDict = newDict()\r\n\r\nusageDict[\"Fans OK\"] = ok\r\nusageDict[\"Fans NOK\"] = loss\r\n \r\nusageDict", + "graphConfig": { + "colorOverrides": { + "Fans OK": "green", + "Fans NOK": "red" + }, + "mapToHostname": true, + "unit": "fans" + }, + "visualization": "donutGraph" + }, + "location": "main", + "parent": "" + }, + { + "id": "bd45f03b-1c5c-40b2-b94b-1e8b5df1322e", + "name": "TEMPERATURE state", + "position": { + "x": 8, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 9 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let temperature = `*:/Sysdb/environment/archer/temperature/status/cell/1/*`\r\nlet data = temperature | where(dictHasKey(temperature, _key) == true) | recmap(2, (merge(_value)[\"alertRaised\"]))\r\n\r\nlet loss = 0\r\nlet ok = 0\r\n\r\nfor deviceKey, deviceValue in data{\r\n for sensorKey, sensorValue in deviceValue{\r\n if sensorValue==false{\r\n let ok = ok +1\r\n } else{\r\n let loss = loss +1\r\n }\r\n }\r\n}\r\n\r\nlet usageDict = newDict()\r\n\r\nusageDict[\"Temp OK\"] = ok\r\nusageDict[\"Temp NOK\"] = loss\r\n \r\nusageDict", + "graphConfig": { + "colorOverrides": { + "Temp NOK": "red", + "Temp OK": "green" + }, + "mapToHostname": true, + "unit": "sensors" + }, + "visualization": "donutGraph" + }, + "location": "main", + "parent": "" + }, + { + "id": "c5b66b76-ddd7-4766-83b0-846fd82c3f53", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "", + "inputName": "SWITCH_ROLE", + "inputSource": "devices", + "inputWidgetId": "c5b66b76-ddd7-4766-83b0-846fd82c3f53", + "tagLabel": "switch_role" + }, + "location": "inputs", + "parent": "" + }, + { + "id": "9eb232c0-82f3-48a4-961b-0b50927394f0", + "name": "", + "position": { + "x": 4, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "", + "inputName": "POD_NAME", + "inputSource": "devices", + "inputWidgetId": "9eb232c0-82f3-48a4-961b-0b50927394f0", + "tagLabel": "pod_name" + }, + "location": "inputs", + "parent": "" + } + ], + "lastUpdated": 1691169204125, + "lastUpdatedBy": "tamas" + } + ] +} \ No newline at end of file diff --git a/_downloads/5c03cb3fbe2ee11ca185dc070940241b/if_info_filter_port_desc.json b/_downloads/5c03cb3fbe2ee11ca185dc070940241b/if_info_filter_port_desc.json new file mode 100644 index 0000000..259a8b0 --- /dev/null +++ b/_downloads/5c03cb3fbe2ee11ca185dc070940241b/if_info_filter_port_desc.json @@ -0,0 +1,88 @@ +{ + "dashboards": [ + { + "key": "76e82968-cffe-48ff-9857-4d80f710e344", + "createdAt": [ + 470032826, + 1570 + ], + "createdBy": "cvpadmin", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Gather Interface Information with a filter on Port Description", + "description": "", + "widgets": [ + { + "id": "dc092a39-346a-4c47-9b3f-c9e3cdf97ecd", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 22, + "height": 11 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "# Requires a variable input named PortDescription\n\n# Get the interface names and descriptions\nlet intfConfig = `*:/Sysdb/interface/config/eth/phy/slice/1/intfConfig/*` | recmap(2, merge(_value))\nlet i = 0\nlet result = newDict()\nfor deviceKey, deviceVal in intfConfig {\n for interfaceKey, interfaceVal in deviceVal {\n if reMatch(interfaceVal[\"description\"], _PortDescription) {\n result[i] = newDict()\n result[i][\"Switch\"] = deviceKey\n result[i][\"Interface\"] = interfaceKey\n result[i][\"Port Description\"] = interfaceVal[\"description\"]\n let i = i + 1\n }\n }\n}\n\n# Get the LLDP Neighbour Information\nlet lldpPeers = `*:/Sysdb/l2discovery/lldp/status/local/1/portStatus/*/remoteSystem/*` | recmap(3, merge(_value))\nfor deviceKey, deviceVal in result {\n for lldpKey, lldpVal in lldpPeers {\n if lldpKey == deviceVal[\"Switch\"] {\n for interfaceKey, interfaceVal in lldpVal {\n if interfaceKey == deviceVal[\"Interface\"] {\n let values = interfaceVal[dictKeys(interfaceVal)[0]]\n result[deviceKey][\"Remote LLDP Hostname\"] = values[\"sysName\"][\"value\"][\"value\"]\n result[deviceKey][\"Remote LLDP PortID\"] = values[\"msap\"][\"portIdentifier\"][\"portId\"]\n result[deviceKey][\"Chassis Identifier\"] = values[\"msap\"][\"chassisIdentifier\"][\"chassisId\"]\n }\n }\n }\n }\n}\n\n# Get the L2 Interface Information (VLAN Info, etc)\nlet switchIntfConfig = `*:/Sysdb/bridging/switchIntfConfig/switchIntfConfig/*` | recmap(2, merge(_value))\nfor deviceKey, deviceVal in result {\n for switchKey, switchVal in switchIntfConfig {\n if switchKey == deviceVal[\"Switch\"] {\n for interfaceKey, interfaceVal in switchVal {\n if interfaceKey == deviceVal[\"Interface\"] {\n result[deviceKey][\"Switchport Mode\"] = interfaceVal[\"switchportMode\"][\"Name\"]\n result[deviceKey][\"Access VLAN\"] = interfaceVal[\"accessVlan\"][\"value\"]\n result[deviceKey][\"Trunked VLANs\"] = interfaceVal[\"trunkAllowedVlans\"]\n }\n }\n }\n }\n}\n\n# Get the Interface Status Info\nlet intfStatus = `*:/Sysdb/interface/status/eth/phy/slice/1/intfStatus/*` | recmap(2, merge(_value))\nfor deviceKey, deviceVal in result {\n for switchKey, switchVal in intfStatus {\n if switchKey == deviceVal[\"Switch\"] {\n for intfKey, intfVal in switchVal {\n if intfKey == deviceVal[\"Interface\"] {\n result[deviceKey][\"Status\"] = intfVal[\"operStatus\"][\"Name\"]\n result[deviceKey][\"Speed\"] = intfVal[\"speedEnum\"][\"Name\"]\n }\n }\n }\n }\n}\nresult\n", + "graphConfig": { + "defaultSort": { + "key": "Switch" + }, + "columns": { + "Switch": { + "mapToHostname": true + } + }, + "columnOrders": { + "key": 1, + "Switch": 2, + "Interface": 3, + "Status": 4, + "Switchport Mode": 5, + "Trunked VLANs": 6, + "Access VLAN": 7, + "Port Description": 8, + "Chassis Identifier": 9, + "Remote LLDP Hostname": 10, + "Remote LLDP PortID": 11, + "Speed": 12 + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "7803f7c2-439c-4768-a8a7-fdcf8dc3566d", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 6, + "height": 2 + }, + "type": "variable-widget", + "inputs": { + "defaultValue": "", + "inputName": "PortDescription", + "inputType": "FreeForm", + "variableType": "String" + }, + "location": "inputs", + "parent": "" + } + ], + "lastUpdated": 1691022276885, + "lastUpdatedBy": "cvpadmin" + } + ] +} \ No newline at end of file diff --git a/_downloads/79cf4fd868bf214c63f967d996b58bd5/fn72.json b/_downloads/79cf4fd868bf214c63f967d996b58bd5/fn72.json new file mode 100644 index 0000000..f1c52a1 --- /dev/null +++ b/_downloads/79cf4fd868bf214c63f967d996b58bd5/fn72.json @@ -0,0 +1,42 @@ +{ + "dashboards": [ + { + "key": "b946c25e-8c85-49f4-89ee-e57266ea4603", + "createdAt": [ + 399285739, + 1569 + ], + "createdBy": "cvpadmin", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Devices affected by FN72", + "description": "", + "widgets": [ + { + "id": "303fe171-8fd2-406f-8252-fc6cc8b1aba6", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 11, + "height": 14 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let inventory = merge(`analytics:/DatasetInfo/Devices`)\nlet affected = newDict()\nlet id = 1\nfor dkey, dval in inventory{\n \n let serial_slice = strCut(dkey,3,7)\n let model = dval[\"modelName\"]\n let hostname = dval[\"hostname\"]\n if strContains(model, \"7280SR3-48YC8\"){\n if strHasPrefix(dkey, \"JPE\"){\n if num(serial_slice) < 2131{\n affected[id] = newDict()\n affected[id][\"Hostname\"] = hostname\n affected[id][\"Serial Number\"] = dkey\n affected[id][\"Model\"] = model\n }\n }\n if strHasPrefix(dkey,\"JAS\"){\n if num(serial_slice) < 2041{\n affected[id] = newDict()\n affected[id][\"Hostname\"] = hostname\n affected[id][\"Serial Number\"] = dkey\n affected[id][\"Model\"] = model\n }\n }\n let id = id + 1\n }\n if strContains(model, \"7280SR3K-48YC8\"){\n if strHasPrefix(dkey, \"JPE\"){\n if num(serial_slice) < 2134{\n affected[id] = newDict()\n affected[id][\"Hostname\"] = hostname\n affected[id][\"Serial Number\"] = dkey\n affected[id][\"Model\"] = model\n }\n }\n if strHasPrefix(dkey, \"JAS\"){\n if num(serial_slice) < 2041{\n affected[id] = newDict()\n affected[id][\"Hostname\"] = hostname\n affected[id][\"Serial Number\"] = dkey\n affected[id][\"Model\"] = model\n }\n }\n let id = id + 1\n }\n \n}\naffected", + "visualization": "table" + }, + "location": "main" + } + ], + "lastUpdated": 1685150082387, + "lastUpdatedBy": "cvpadmin" + } + ] + } \ No newline at end of file diff --git a/_downloads/85a1c3cb31cdfce5ab78777987ef2a49/system_health_check.json b/_downloads/85a1c3cb31cdfce5ab78777987ef2a49/system_health_check.json new file mode 100644 index 0000000..c09aefe --- /dev/null +++ b/_downloads/85a1c3cb31cdfce5ab78777987ef2a49/system_health_check.json @@ -0,0 +1,310 @@ +{ + "dashboards": [ + { + "key": "597bf49c-dcbf-460c-bb2e-b48d182f8d7c", + "createdAt": [ + 1005976962, + 1574 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "System Health Check", + "description": "", + "widgets": [ + { + "id": "d1a23e52-595c-4210-886b-970269fd1892", + "name": "CPU Utilization and Boot Timeper Device", + "position": { + "x": 11, + "y": 10 + }, + "dimensions": { + "width": 8, + "height": 9 + }, + "type": "metrics-widget-table", + "inputs": { + "components": [], + "isTokenSearchEnabled": true, + "metricKeys": [ + "DEVICE_CPU", + "DEVICE_BOOTTIME_METRIC" + ], + "metricSource": "devices", + "selectedCustomTags": [ + "POD_NAME", + "SWITCH_ROLE" + ], + "tags": "", + "viewType": "metric" + }, + "location": "main", + "parent": "" + }, + { + "id": "5418b3cc-94e1-4a5b-b20d-871415d2f84b", + "name": "Used Memory per Device over Time", + "position": { + "x": 0, + "y": 19 + }, + "dimensions": { + "width": 11, + "height": 9 + }, + "type": "metrics-widget", + "inputs": { + "components": [], + "isTokenSearchEnabled": true, + "metricKeys": [ + "DEVICE_USED_MEMORY" + ], + "metricSource": "devices", + "selectedCustomTags": [ + "POD_NAME", + "SWITCH_ROLE" + ], + "tags": "", + "viewType": "metric" + }, + "location": "main", + "parent": "" + }, + { + "id": "fb792084-6697-4ea4-824d-712897004301", + "name": "CPU Utilization per Device over Time", + "position": { + "x": 0, + "y": 10 + }, + "dimensions": { + "width": 11, + "height": 9 + }, + "type": "metrics-widget", + "inputs": { + "components": [], + "isTokenSearchEnabled": true, + "metricKeys": [ + "DEVICE_CPU" + ], + "metricSource": "devices", + "selectedCustomTags": [ + "POD_NAME", + "SWITCH_ROLE" + ], + "tags": "", + "viewType": "metric" + }, + "location": "main", + "parent": "" + }, + { + "id": "f9eb222c-3fe1-4163-88b8-04ec47e27243", + "name": "Usage in Percent of /mnt/flash all", + "position": { + "x": 11, + "y": 19 + }, + "dimensions": { + "width": 8, + "height": 9 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\r\nlet devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/<_SWITCH_ROLE>/elements`)\r\n\r\nlet deviceDisks = `analytics:/Devices/*/versioned-data/hardware/disk/\\/mnt\\/flash`\r\n\r\nif str(_POD_NAME) == \"\" && str(_SWITCH_ROLE) == \"\" {\r\n let data = deviceDisks\r\n} else {\r\n if str(_SWITCH_ROLE) == \"\" {\r\n let data = deviceDisks | where(strContains(str(devices), _key))\r\n } else {\r\n if str(_POD_NAME) == \"\" {\r\n let data = deviceDisks | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n } else {\r\n let data = deviceDisks | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n }\r\n }\r\n}\r\n\r\ndata | map(merge(_value) | fields(\"usedPartitionPercent\"))", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true, + "columnTitle": "Device" + }, + "usedPartitionPercent": { + "mapToHostname": false, + "type": "number", + "decimals": 2, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 60, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 60, + "to": 80, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 80, + "to": 100, + "result": { + "color": "red", + "index": 2 + } + } + } + ], + "showDotIndicator": true, + "unit": "%" + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "6b78a2d2-3c6a-446c-b12c-0ba6db4bdbd2", + "name": "Disk /mnt/flash State", + "position": { + "x": 12, + "y": 0 + }, + "dimensions": { + "width": 7, + "height": 10 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let data =`analytics:/Devices/*/versioned-data/hardware/disk/\\/mnt\\/flash`\r\nlet data = data | map(merge(_value) | fields(\"usedPartitionPercent\"))\r\n\r\nlet test = 0\r\nlet i60 = 0\r\nlet b6080 = 0\r\nlet s80 = 0\r\n\r\nfor device, deviceData in data {\r\n if dictHasKey(deviceData, \"usedPartitionPercent\") {\r\n let test = deviceData[\"usedPartitionPercent\"]\r\n if test < 60 {\r\n let i60=i60+1\r\n }\r\n if test < 80 && test > 60 {\r\n let b6080=b6080+1\r\n }\r\n if test > 80 {\r\n let s80=s80+1\r\n }\r\n }\r\n}\r\n\r\nlet usageDict = newDict()\r\n\r\nusageDict[\"Disk < 60%\"] = i60\r\nusageDict[\"60% < Disk < 80%\"] = b6080\r\nusageDict[\"Disk > 80%\"] = s80\r\n\r\nusageDict\r\n", + "graphConfig": { + "colorOverrides": { + "Disk < 60%": "green", + "60% < Disk < 80%": "yellow", + "Disk > 80%": "red" + }, + "mapToHostname": true, + "unit": "devices" + }, + "visualization": "donutGraph" + }, + "location": "main", + "parent": "" + }, + { + "id": "e6843920-1bcb-4ee5-8318-d99119760d21", + "name": "CPU Utilization in the Fabric", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 6, + "height": 10 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let data =`analytics:/Devices/*/versioned-data/hardware/cpu/total/aggregate/1m`\r\nlet data = data | map(merge(_value) | fields(\"util\"))\r\n\r\nlet test = 0\r\nlet i60 = 0\r\nlet b6080 = 0\r\nlet s80 = 0\r\n\r\nfor device, deviceData in data {\r\n if dictHasKey(deviceData, \"util\") {\r\n let test = deviceData[\"util\"][\"avg\"]\r\n if test < 60 {\r\n let i60=i60+1\r\n }\r\n if test < 80 && test > 60 {\r\n let b6080=b6080+1\r\n }\r\n if test > 80 {\r\n let s80=s80+1\r\n }\r\n }\r\n}\r\n\r\nlet usageDict = newDict()\r\n\r\nusageDict[\"CPU < 60%\"] = i60\r\nusageDict[\" 60% < CPU < 80%\"] = b6080\r\nusageDict[\"CPU > 80%\"] = s80\r\n\r\nusageDict\r\n", + "graphConfig": { + "colorOverrides": { + "CPU > 80%": "red", + "CPU < 60%": "green", + " 60% < CPU < 80%": "yellow" + }, + "mapToHostname": true, + "unit": "devices" + }, + "visualization": "donutGraph" + }, + "location": "main", + "parent": "" + }, + { + "id": "04079929-781d-4dbc-bb16-cd6f65cfe970", + "name": "Memory Usage in the Fabric", + "position": { + "x": 6, + "y": 0 + }, + "dimensions": { + "width": 6, + "height": 10 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let data =`analytics:/Devices/*/versioned-data/hardware/meminfo/memoryUsage`\r\nlet data = data | map(merge(_value) | fields(\"usedMemoryPercent\"))\r\n\r\nlet test = 0\r\nlet i60 = 0\r\nlet b6070 = 0\r\nlet s70 = 0\r\n\r\nfor device, deviceData in data {\r\n if dictHasKey(deviceData, \"usedMemoryPercent\") {\r\n let test = deviceData[\"usedMemoryPercent\"]\r\n if test < 70 {\r\n let i60=i60+1\r\n }\r\n if test < 80 && test > 70 {\r\n let b6070=b6070+1\r\n }\r\n if test > 70 {\r\n let s70=s70+1\r\n }\r\n }\r\n}\r\n\r\nlet usageDict = newDict()\r\n\r\nusageDict[\"Memory < 60%\"] = i60\r\nusageDict[\"60% < Memory < 70%\"] = b6070\r\nusageDict[\"Memory > 70%\"] = s70\r\n\r\nusageDict", + "graphConfig": { + "colorOverrides": { + "Memory < 60%": "green", + "60% < Memory < 70%": "yellow", + "Memory > 70%": "red" + }, + "unit": "devices" + }, + "visualization": "donutGraph" + }, + "location": "main", + "parent": "" + }, + { + "id": "b101a5e2-96c8-4874-9ffa-f20ea2a03230", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "", + "inputName": "SWITCH_ROLE", + "inputSource": "devices", + "selectedCustomTags": [], + "tagLabel": "switch_role", + "tags": "" + }, + "location": "inputs", + "parent": "" + }, + { + "id": "1ab1af02-21ae-4fbc-98db-ef027459fbc5", + "name": "", + "position": { + "x": 4, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "", + "inputName": "POD_NAME", + "inputSource": "devices", + "selectedCustomTags": [], + "tagLabel": "pod_name", + "tags": "" + }, + "location": "inputs", + "parent": "" + } + ], + "lastUpdated": 1691082198455, + "lastUpdatedBy": "tamas" + } + ] +} \ No newline at end of file diff --git a/_downloads/85d2a0f19869dc2e8c05e4f710e97e1f/vrfs_in_vlans.json b/_downloads/85d2a0f19869dc2e8c05e4f710e97e1f/vrfs_in_vlans.json new file mode 100644 index 0000000..28627e1 --- /dev/null +++ b/_downloads/85d2a0f19869dc2e8c05e4f710e97e1f/vrfs_in_vlans.json @@ -0,0 +1,139 @@ +{ + "dashboards": [ + { + "key": "6e20493e-d5d7-460c-8d1e-2527bf1f2c91", + "createdAt": [ + 824818590, + 1574 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "VLANs in VRFs", + "description": "This dashboards displays the VLANs configured for the different VRFs and allows also to display VLANs for a specific VRF", + "widgets": [ + { + "id": "46fa7d4d-0fe1-4467-b63b-ce93fc303250", + "name": "List of Configured VLANs per VRFs", + "position": { + "x": 0, + "y": 9 + }, + "dimensions": { + "width": 15, + "height": 10 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "# Get the VRF configuration for all routed interfaces for all devices\nlet data=`*:/Sysdb/l3/intf/config/intfConfig/*`\n\n# Build a new dictionary and select only the SVIs and the VRFs they are configured in\nlet res = newDict()\nlet id = 0\nfor deviceKey, deviceValue in data {\n for interfaceKey, interfaceValue in deviceValue {\n if strContains(interfaceKey, \"Vlan\"){\n let id = id + 1\n res[id] = newDict()\n res[id][\"Device\"] = deviceKey\n res[id][\"Interfaces\"] = interfaceKey\n res[id][\"VRFs\"] = merge(interfaceValue)[\"vrf\"][\"value\"]\n } \n }\n}\nres\n", + "graphConfig": { + "columns": { + "VRFs": { + "colorMappings": [ + { + "type": "value", + "options": { + "default": { + "color": "green", + "index": 0 + }, + "TENANT": { + "color": "blue", + "index": 1 + } + } + } + ], + "unit": "", + "type": "string", + "showDotIndicator": false + }, + "Device": { + "mapToHostname": true + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "0600518b-4e20-420f-8b56-0af695e4a98e", + "name": "VLANs in VRF", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 15, + "height": 9 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "# Get the VRF configuration for all routed interfaces for all devices\nlet data=`*:/Sysdb/l3/intf/config/intfConfig/*`\n\n# Build a new dictionary and select only the SVIs that are configured in a specific VRF\nlet res = newDict()\nlet id = 0\nfor deviceKey, deviceValue in data {\n for interfaceKey, interfaceValue in deviceValue {\n if strContains(interfaceKey, \"Vlan\"){\n let data1 = merge(interfaceValue)\n if data1[\"vrf\"][\"value\"] == _VRF {\n let id = id + 1\n res[id] = newDict()\n res[id][\"Device\"] = deviceKey\n res[id][\"Interfaces\"] = interfaceKey\n res[id][\"VRFs\"] = _VRF\n }\n } \n }\n}\nres", + "graphConfig": { + "columns": { + "VRFs": { + "colorMappings": [ + { + "type": "value", + "options": { + "default": { + "color": "green", + "index": 0 + }, + "TENANT": { + "color": "blue", + "index": 1 + } + } + } + ] + }, + "Device": { + "mapToHostname": true + } + }, + "columnOrders": { + "key": 1, + "Device": 2, + "Interfaces": 3, + "VRFs": 4 + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "fe31ae70-fcd4-4cbc-975a-29e1792b3157", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 6, + "height": 2 + }, + "type": "variable-widget", + "inputs": { + "defaultValue": "default", + "inputName": "VRF", + "variableType": "String" + }, + "location": "inputs", + "parent": "" + } + ], + "lastUpdated": 1690894662946, + "lastUpdatedBy": "tamas" + } + ] +} \ No newline at end of file diff --git a/_downloads/9b85207f1a78bdee8c6d81b58a857859/abootfn44.json b/_downloads/9b85207f1a78bdee8c6d81b58a857859/abootfn44.json new file mode 100644 index 0000000..8b31ddc --- /dev/null +++ b/_downloads/9b85207f1a78bdee8c6d81b58a857859/abootfn44.json @@ -0,0 +1,50 @@ +{ + "dashboards": [ + { + "key": "8c739108-62a3-44be-b8be-11e889e263bc", + "createdAt": [ + 1032706872, + 1574 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "FN44", + "description": "", + "widgets": [ + { + "id": "5fb458a8-8b8e-48e7-9b79-e66253f24e76", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 14, + "height": 22 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let fixed = `*:/Sysdb/hardware/entmib/fixedSystem`\nlet chassis = `*:/Sysdb/hardware/entmib/chassis/cardSlot/*/card`\nlet result = newDict()\n\nfor deviceKey, deviceValue in fixed{\n let fx_temp = merge(deviceValue)\n if dictHasKey(fx_temp , \"firmwareRev\") && fx_temp[\"firmwareRev\"] != \"\"{\n result[deviceKey] = newDict() | setFields(\"Aboot version\", fx_temp[\"firmwareRev\"])\n }\n\n}\nfor deviceKey, deviceValue in chassis {\n let cx_tmp = chassis[deviceKey] | map(merge(_value))\n for card in cx_tmp{\n if dictHasKey(card, \"firmwareRev\") && card[\"firmwareRev\"] != \"\" {\n result[deviceKey] = newDict() | setFields(\"Aboot version\", card[\"firmwareRev\"])\n }\n }\n\n}\n\nlet re = \"Aboot-norcal\\d+-(\\d+)\\.(\\d+)\\.(\\d+).*\"\n\nfor deviceKey, deviceValue in result{\n let aboot = reFindCaptures(deviceValue[\"Aboot version\"], re)\n if length(aboot) > 0 {\n\n if num(aboot[0][1]) == 4 {\n if num(aboot[0][2]) == 0 && num(aboot[0][3]) < 7 {\n result[deviceKey][\"affected\"] = \"🔥 True\"\n }\n if num(aboot[0][2]) == 1 && num(aboot[0][3]) < 1 {\n result[deviceKey][\"affected\"] = \"🔥 True\"\n }\n }\n if num(aboot[0][1]) == 6 {\n if num(aboot[0][2]) == 0 && num(aboot[0][3]) < 9 {\n result[deviceKey][\"affected\"] = \"🔥 True\"\n }\n if num(aboot[0][2]) == 1 && num(aboot[0][3]) < 7 {\n result[deviceKey][\"affected\"] = \"🔥 True\"\n } else {\n result[deviceKey][\"affected\"] = \"✅ False\"\n }\n } else {\n result[deviceKey][\"affected\"] = \"✅ False\"\n }\n\n } else {\n result[deviceKey][\"affected\"] = \"✅ False\"\n }\n\n}\nresult\n", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + } + ], + "lastUpdated": 1691102387240, + "lastUpdatedBy": "tamas" + } + ] +} \ No newline at end of file diff --git a/_downloads/a0a8f49d1aaa01cc3df5c22e60343d53/admin_state.json b/_downloads/a0a8f49d1aaa01cc3df5c22e60343d53/admin_state.json new file mode 100644 index 0000000..46a2fcd --- /dev/null +++ b/_downloads/a0a8f49d1aaa01cc3df5c22e60343d53/admin_state.json @@ -0,0 +1,88 @@ +{ + "dashboards": [ + { + "key": "7c1195b0-be81-4f16-b066-5265ea904d40", + "createdAt": [ + 414026146, + 1570 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Interface Admin Status", + "description": "", + "widgets": [ + { + "id": "84631492-7f46-40e6-adfb-3279e0eb9960", + "name": "", + "position": { + "x": 11, + "y": 0 + }, + "dimensions": { + "width": 7, + "height": 12 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let data = `analytics:/Devices/<_device>/versioned-data/OpenConfig/interfaces/interface/*/state` \nlet res = newDict()\nfor key, value in data{\n let temp = merge(value)\n res[key[\"name\"]] = newDict() | setFields(\"Status\", temp[\"admin-status\"]) \n}\nres", + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "f7da75be-066e-44f3-9b48-cfb172257f57", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 8, + "height": 10 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let data = `<_device>:/Sysdb/interface/config/eth/phy/slice/*/intfConfig/*`\nlet res = newDict()\nfor cell in data{\n for interface, value in cell{\n let status = merge(value)[\"adminEnabledStateLocal\"][\"Name\"]\n # if an interface was never shutdown the state is unknownEnabledState\n if status == \"unknownEnabledState\"{\n let status = \"enabled\"\n }\n res[interface] = newDict() | setFields(\"Status\", status)\n \n }\n}\nres", + "graphConfig": { + "columns": { + "Status": {} + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "64d655a3-76d5-4a31-a985-ccf6c551b29a", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "BAD032986065E8DC14CBB6472EC314A6", + "inputName": "device", + "inputSource": "devices", + "tagLabel": "device" + }, + "location": "inputs", + "parent": "" + } + ], + "lastUpdated": 1686190218947, + "lastUpdatedBy": "tamas" + } + ] + } \ No newline at end of file diff --git a/_downloads/a7b0d5b48cb41c016b861f9f82a02fab/webinar03_bgp_states.json b/_downloads/a7b0d5b48cb41c016b861f9f82a02fab/webinar03_bgp_states.json new file mode 100644 index 0000000..fd8aa57 --- /dev/null +++ b/_downloads/a7b0d5b48cb41c016b861f9f82a02fab/webinar03_bgp_states.json @@ -0,0 +1,491 @@ +{ + "dashboards": [ + { + "key": "da03ca9f-c409-44d4-823d-1a65a5855e4b", + "createdAt": [ + 1039030138, + 1574 + ], + "createdBy": "cvpadmin", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Webinar03 - BGP States", + "description": "#palantiri", + "widgets": [ + { + "id": "96d68c08-c11f-452d-99f2-d0c5078f2d08", + "name": "BGP Session Details in the Default VRF for all Devices", + "position": { + "x": 5, + "y": 0 + }, + "dimensions": { + "width": 19, + "height": 13 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\nlet bgpPeerInfoStatusEntry = `*:/Sysdb/cell/1/routing/bgp/export/vrfBgpPeerInfoStatusEntryTable/default/bgpPeerInfoStatusEntry/*`\nif str(_POD_NAME) == \"\" {\n let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`\n} else {\n let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\n}\nlet bgpPeerStatisticsEntry = `*:/Smash/routing/bgp/bgpPeerInfoStatus/default/bgpPeerStatisticsEntry`\nlet afis = newDict()\nlet afi_name = newDict()\nlet bgpPeerAfiSafiActive = newDict() | setFields(\"l2vpnEvpn\", 3, \"ipv4Unicast\", 1)\nlet bgpPeerAfiSafiActiveName = newDict() | setFields(\"l2vpnEvpn\",\"L2VPN EVPN\", \"ipv4Unicast\", \"IPv4 Unicast\")\n\n# This is the table\nlet result = newDict()\nlet id = 0\n# Lets loop over every device\nfor device, deviceSessions in bgpNeighbors{\n # And each session on the devices\n for ip, sessionData in deviceSessions{\n let data = merge(sessionData)\n # Add one to the ID\n let id = id + 1\n result[id] = newDict()\n # This is where we add the various columns\n result[id][\"0. Device\"] = device\n result[id][\"1. Status\"] = data[\"bgpState\"][\"Name\"]\n result[id][\"2. Peering Address\"] = data[\"bgpPeerLocalAddr\"]\n result[id][\"3. Neighbor Address\"] = data[\"key\"]\n result[id][\"4. Neighbor AS\"] = data[\"bgpPeerAs\"][\"value\"]\n if dictHasKey(bgpPeerInfoStatusEntry, device) && dictHasKey(bgpPeerStatisticsEntry, device) {\n let test = merge(bgpPeerInfoStatusEntry[device][ip])\n for kafi, kval in test[\"bgpPeerAfiSafiActive\"]{\n if kval == true {\n afis[device] = bgpPeerAfiSafiActive[kafi]\n afi_name[device] = bgpPeerAfiSafiActiveName[kafi]\n }\n }\n let bgpPeerAfiSafiStats = merge(bgpPeerStatisticsEntry[device])\n if dictHasKey(afis, device){\n result[id][\"6. PfxRcd\"] = bgpPeerAfiSafiStats[ip][\"bgpPeerAfiSafiStats\"][afis[device]][\"prefixIn\"]\n result[id][\"7. PfxAcc\"] = bgpPeerAfiSafiStats[ip][\"bgpPeerAfiSafiStats\"][afis[device]][\"prefixAcceptedIn\"]\n }\n result[id][\"8. Up/Down\"] = str(duration(1000000000*round(num(now() - time(data[\"bgpPeerIntoOrOutOfEstablishedTime\"]*1000000000))/1000000000)))\n }\n }\n}\nresult", + "graphConfig": { + "columns": { + "0. Device": { + "mapToHostname": true + }, + "1. Status": { + "colorMappings": [ + { + "type": "value", + "options": { + "Established": { + "color": "green", + "index": 0 + }, + "Active": { + "color": "yellow", + "index": 1 + }, + "Idle": { + "color": "orange", + "index": 2 + }, + "OpenSent": { + "color": "purple", + "index": 3 + }, + "Connect": { + "color": "red9", + "index": 4 + } + } + } + ] + }, + "6. PfxRcd": { + "colorMappings": [ + { + "type": "value", + "options": { + "0": { + "color": "red", + "index": 0 + } + } + } + ] + }, + "7. PfxAcc": { + "colorMappings": [ + { + "type": "value", + "options": { + "0": { + "color": "red", + "index": 0 + } + } + } + ] + } + }, + "defaultSort": { + "key": "key" + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "fa272d33-9299-40d1-9aa1-fdb215aa5d75", + "name": "BGP Session Status", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 5, + "height": 13 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "\nlet devices = merge(`analytics:/tags/labels/devices/scenario/value/<_scenario>/elements`) \nlet neighbors = `analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*` | where(strContains(str(devices), _key))\n\n# Dict to store the states and counts\nlet res = newDict()\n# Loop over each device\nfor device, deviceSessions in neighbors{\n # Loop over each session on each device\n for ip, sessionData in deviceSessions{\n let data = merge(sessionData)\n \n # Have we used this status yet?\n let status = data[\"bgpState\"][\"Name\"]\n if !dictHasKey(res, status) {\n # If not lets set it use count to zero\n res[status] = 0\n }\n # Add one to the total times this status is used\n res[status] = res[status] + 1\n }\n}\nres", + "graphConfig": { + "colorOverrides": { + "Idle": "orange", + "OpenSent": "purple", + "Established": "green", + "Active": "yellow", + "Connect": "red9" + }, + "mapToHostname": true, + "unit": "sessions" + }, + "visualization": "donutGraph" + }, + "location": "main", + "parent": "" + }, + { + "id": "fab483f7-74fd-4715-9ffa-8f7fff2547b1", + "name": "BGP Sessions that are Not Established", + "position": { + "x": 0, + "y": 13 + }, + "dimensions": { + "width": 20, + "height": 7 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\r\n\r\nif str(_POD_NAME) == \"\" {\r\n let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`\r\n} else {\r\n let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n}\r\n\r\n# This is the table\r\nlet res = newDict()\r\nlet id = 0\r\n# Lets loop over every device\r\nfor device, deviceSessions in bgpNeighbors{\r\n # And each session on the devices\r\n for ip, sessionData in deviceSessions{\r\n let data = merge(sessionData)\r\n # Add one to the ID\r\n let id = id + 1\r\n res[id] = newDict()\r\n # This is where we add the various columns\r\n res[id][\"0. Device\"] = device\r\n res[id][\"1. Status\"] = data[\"bgpState\"][\"Name\"]\r\n res[id][\"2. Peering Address\"] = data[\"bgpPeerLocalAddr\"]\r\n res[id][\"3. Neighbor Address\"] = data[\"key\"]\r\n res[id][\"4. Neighbor AS\"] = data[\"bgpPeerAs\"][\"value\"]\r\n }\r\n}\r\n\r\nres | where(_value[\"1. Status\"] != \"Established\")\r\n", + "graphConfig": { + "columns": { + "0. Device": { + "mapToHostname": true + }, + "1. Status": { + "colorMappings": [ + { + "type": "value", + "options": { + "Active": { + "color": "yellow", + "index": 0 + }, + "Connect": { + "color": "red9", + "index": 1 + }, + "Idle": { + "color": "orange", + "index": 2 + }, + "OpenSent": { + "color": "purple", + "index": 3 + } + } + } + ] + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "b9f10a46-b753-4cbd-a136-1921103fbc57", + "name": "BGP Session historical state tracker", + "position": { + "x": 8, + "y": 22 + }, + "dimensions": { + "width": 10, + "height": 15 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "# BGP Session historical state tracker\nlet data = `analytics:/Devices/<_bgpDevice>/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`[4h]\nlet res = newDict()\nfor ip, tseries in data {\n for timestamp, values in tseries {\n # only show selected neighbors or all if none selected\n if length(_NeighborIP) == 0 || dictHasKey(_NeighborIP, ip){\n res[str(timestamp)] = newDict() | setFields(ip, dictHasKey(values, \"bgpState\") ? values[\"bgpState\"][\"Name\"] : 0)\n }\n }\n}\nres\n", + "graphConfig": { + "columns": { + "key": { + "columnTitle": "Time" + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "815e2d60-1f50-4e83-afe7-b61459ac8d1e", + "name": "Flaps in the last 4 hours", + "position": { + "x": 0, + "y": 25 + }, + "dimensions": { + "width": 8, + "height": 12 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\n\nif str(_POD_NAME) == \"\" {\n let data =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`[4h]\n} else {\n let data =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`[4h] | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\n}\n\nlet result = newDict()\nfor sn, deviceValues in data{\n let count = 0\n for ip, tseries in deviceValues{\n for timestamp, values in tseries {\n if dictHasKey(values, \"bgpState\"){\n if values[\"bgpState\"][\"Name\"] == \"Established\"{\n let count = count +1\n }\n }\n }\n }\n result[sn] = newDict() | setFields(\"Flaps\", count-1)\n}\nresult\n", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true, + "columnTitle": "Hostname", + "colorMappings": [ + { + "type": "range", + "options": { + "from": 2, + "to": 10, + "result": { + "color": "purple", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 100, + "to": 10000, + "result": { + "color": "red", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 10, + "to": 100, + "result": { + "color": "yellow", + "index": 2 + } + } + } + ], + "showDotIndicator": false + }, + "Flaps": { + "showDotIndicator": true, + "colorMappings": [ + { + "type": "value", + "options": { + "0": { + "color": "green", + "index": 3 + } + } + }, + { + "type": "range", + "options": { + "from": 10, + "to": 50, + "result": { + "color": "yellow", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 50, + "to": 10000, + "result": { + "color": "red", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 2, + "to": 10, + "result": { + "color": "purple3", + "index": 2 + } + } + } + ] + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "9f6e3f1a-dc19-451f-9fdf-9de3a62c1bde", + "name": "", + "position": { + "x": 0, + "y": 20 + }, + "dimensions": { + "width": 8, + "height": 5 + }, + "type": "text-widget", + "inputs": { + "textContent": "# BGP Flaps\n\nIdeally these should be zero!\nLight the Warning Beacons of Gondor 🔥🔥 otherwise.\n\nCheck for more details in the `BGP historical state tracker` nearby ➡️➡️", + "verticalAlignment": 0 + }, + "location": "main", + "styles": { + "hideTitle": true, + "backgroundColor": "", + "hideHorizontalBar": false, + "titleSize": 14 + }, + "parent": "" + }, + { + "id": "756806ca-c9ca-42b0-8389-32eaa3006a85", + "name": "BGP Summary", + "position": { + "x": 0, + "y": 37 + }, + "dimensions": { + "width": 18, + "height": 7 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let bgpPeerInfoStatusEntry = `<_bgpDevice>:/Sysdb/cell/1/routing/bgp/export/vrfBgpPeerInfoStatusEntryTable/default/bgpPeerInfoStatusEntry/*`\nlet data3 = merge(`<_bgpDevice>:/Smash/routing/bgp/bgpPeerInfoStatus/default/bgpPeerStatisticsEntry`)\nlet data2 = `analytics:/Devices/<_bgpDevice>/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*` | map(merge(_value))\nlet afis = newDict()\nlet afi_name = newDict()\nlet result = newDict()\nlet bgpPeerAfiSafiActive = newDict() | setFields(\"l2vpnEvpn\", 3, \"ipv4Unicast\", 1)\nlet bgpPeerAfiSafiActiveName = newDict() | setFields(\"l2vpnEvpn\",\"L2VPN EVPN\", \"ipv4Unicast\", \"IPv4 Unicast\")\nfor k,v in bgpPeerInfoStatusEntry {\n let temp = merge(v)\n for kafi,kval in temp[\"bgpPeerAfiSafiActive\"]{\n if kval == true{\n afis[k] = bgpPeerAfiSafiActive[kafi]\n afi_name[k] = bgpPeerAfiSafiActiveName[kafi]\n }\n }\n result[k] = newDict() | setFields(\"AFI/SAFI\", afi_name[k], \"PfxRcd\", data3[k][\"bgpPeerAfiSafiStats\"][afis[k]][\"prefixIn\"], \"PfxAcc\", data3[k][\"bgpPeerAfiSafiStats\"][afis[k]][\"prefixAcceptedIn\"])\n}\nresult", + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "b7e349d6-4f06-4159-a4e7-79597ba7be9e", + "name": "BGP Syslogs on selected device", + "position": { + "x": 0, + "y": 44 + }, + "dimensions": { + "width": 18, + "height": 12 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let data = `<_bgpDevice>:/Logs/var/log/messages`[4h] | field(\"line\") | where(reMatch(_value, \"BGP\"))\nlet logs = newDict()\nfor timest, logentry in data {\n logs[str(timest)] = newDict()\n logs[str(timest)][\"Log\"] = logentry\n}\nlogs", + "graphConfig": { + "columns": { + "key": { + "columnTitle": "Timestamp" + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "9d02c221-74ff-47dd-9b79-5ef575246c91", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "pod1", + "inputName": "POD_NAME", + "inputSource": "devices", + "tagLabel": "pod_name" + }, + "location": "inputs", + "parent": "" + }, + { + "id": "56a65cc7-7555-459c-9048-29c2146dce86", + "name": "", + "position": { + "x": 4, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "upu_tp", + "inputName": "scenario", + "inputSource": "devices", + "tagLabel": "scenario" + }, + "location": "inputs", + "parent": "" + }, + { + "id": "4fd8d3f2-6e19-4d8b-aec5-521737feeac0", + "name": "", + "position": { + "x": 8, + "y": 20 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "SSJ17500014", + "inputName": "bgpDevice", + "inputSource": "devices", + "tagLabel": "device" + }, + "location": "main", + "styles": { + "hideTitle": false, + "backgroundColor": "", + "hideHorizontalBar": false, + "titleSize": 14 + }, + "parent": "" + }, + { + "id": "33b2f3c9-9f55-43b0-95cb-d16196ee327f", + "name": "", + "position": { + "x": 12, + "y": 20 + }, + "dimensions": { + "width": 6, + "height": 2 + }, + "type": "variable-widget", + "inputs": { + "defaultValue": [], + "inputName": "NeighborIP", + "inputType": "MultiSelect", + "inputWidgetId": "33b2f3c9-9f55-43b0-95cb-d16196ee327f", + "selectData": { + "createOptionsUsingAql": true, + "manualOptions": [], + "query": "`analytics:/Devices/<_bgpDevice>/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`" + }, + "variableType": "String" + }, + "location": "main", + "parent": "" + } + ], + "lastUpdated": 1691110325894, + "lastUpdatedBy": "cvpadmin" + } + ] +} \ No newline at end of file diff --git a/_downloads/ad94948ce7891adf9d9ea5eb46c13d4b/important_pod_count.json b/_downloads/ad94948ce7891adf9d9ea5eb46c13d4b/important_pod_count.json new file mode 100644 index 0000000..e87f1ff --- /dev/null +++ b/_downloads/ad94948ce7891adf9d9ea5eb46c13d4b/important_pod_count.json @@ -0,0 +1,194 @@ +{ + "dashboards": [ + { + "key": "b412d588-e30f-43cb-a6ab-d7c376ae2f77", + "createdAt": [ + 27367582, + 1575 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Important POD Counts", + "description": "", + "widgets": [ + { + "id": "a1121ce4-1f33-4e32-9546-b47053d408a9", + "name": "Total VLAN Count", + "position": { + "x": 0, + "y": 12 + }, + "dimensions": { + "width": 6, + "height": 8 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\r\nlet devicesInCPOD1 = merge(`analytics:/tags/labels/devices/pod_name/value/CPOD1/elements`)\r\n\r\nif str(_POD_NAME) == \"\" {\r\n let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devicesInCPOD1, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(merge(_value))\r\n} else {\r\n let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(merge(_value))\r\n}\r\n\r\nlet numberVlan = newDict()\r\n\r\nfor deviceKey, deviceValue in vlanConfig {\r\n for vlanKey, vlanValue in deviceValue{\r\n numberVlan[vlanKey[\"value\"]] = 1\r\n }\r\n}\r\n\r\nlength(numberVlan)", + "graphConfig": { + "fontColor": "purple", + "fontSize": 120 + }, + "visualization": "singleValue" + }, + "location": "main", + "parent": "" + }, + { + "id": "9181bf71-bf0a-499b-b5f2-ce0e6e251e62", + "name": "Number of Leaf Switches", + "position": { + "x": 0, + "y": 4 + }, + "dimensions": { + "width": 6, + "height": 8 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\r\nlet devicesInCPOD1 = merge(`analytics:/tags/labels/devices/pod_name/value/CPOD1/elements`)\r\n\r\nif str(_POD_NAME) == \"\" {\r\n let leafs = length(devicesInCPOD1) - 2\r\n} else {\r\n let leafs = length(devices) - 2\r\n}\r\n\r\nleafs", + "graphConfig": { + "fontColor": "#e29385", + "fontSize": 120, + "description": "Each POD is composed of Leaf switches and 2 Spine switches. So here, we count the number of devices in the POD and subtract 2 to the total." + }, + "visualization": "singleValue" + }, + "location": "main", + "parent": "" + }, + { + "id": "c60342ef-1827-4e73-81f6-96f10cb2f658", + "name": "Number of VTEPs", + "position": { + "x": 6, + "y": 4 + }, + "dimensions": { + "width": 6, + "height": 8 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\r\nlet devicesInCPOD1 = merge(`analytics:/tags/labels/devices/pod_name/value/CPOD1/elements`)\r\n\r\nif str(_POD_NAME) == \"\" {\r\n let vteps = (length(devicesInCPOD1) - 2 ) / 2\r\n} else {\r\n if strContains(str(_POD_NAME), \"SPOD\") {\r\n let vteps = ((length(devices) - 3) / 2)+1\r\n } else {\r\n let vteps = (length(devices) - 2) / 2\r\n }\r\n}\r\n\r\nvteps", + "graphConfig": { + "fontColor": "#9477ff", + "fontSize": 120, + "description": "The Leaf switches are all deployed in MLAG in this case." + }, + "visualization": "singleValue" + }, + "location": "main", + "parent": "" + }, + { + "id": "54117741-d5f8-49d1-8314-8550047b9d8c", + "name": "Max Size of the Floodlist", + "position": { + "x": 6, + "y": 12 + }, + "dimensions": { + "width": 6, + "height": 8 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\r\nlet devicesInCPOD1 = merge(`analytics:/tags/labels/devices/pod_name/value/CPOD1/elements`)\r\n\r\nif str(_POD_NAME) == \"\" {\r\n let vteps = (length(devicesCPOD1) -2) / 2\r\n let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devicesInCPOD1, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(merge(_value))\r\n} else {\r\n if strContains(str(_POD_NAME), \"SPOD\") {\r\n let vteps = ((length(devices) - 3) / 2)+1\r\n let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(merge(_value))\r\n } else {\r\n let vteps = (length(devices) -2) / 2\r\n let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(merge(_value))\r\n }\r\n}\r\n\r\nlet numberVlan = newDict()\r\n\r\nfor deviceKey, deviceValue in vlanConfig {\r\n for vlanKey, vlanValue in deviceValue{\r\n numberVlan[vlanKey[\"value\"]] = 1\r\n }\r\n}\r\n\r\nlet vlans = length(numberVlan)\r\n\r\nvteps*vlans", + "graphConfig": { + "type": "number", + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 180000, + "result": { + "color": "green3", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 180000, + "to": 210000, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 210000, + "result": { + "color": "red", + "index": 2 + } + } + } + ], + "description": "If every VLAN would be configured on every VTEP in this POD, that would be the floodlist size.\nIn this case:\nFlood list = Number of VTEPs * Total VLAN Count", + "fontSize": 120 + }, + "visualization": "singleValue" + }, + "location": "main", + "parent": "" + }, + { + "id": "aab0c6e4-e4b5-4f85-8e44-31256606db85", + "name": "Note", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 12, + "height": 4 + }, + "type": "text-widget", + "inputs": { + "textContent": "If no POD is chosen in the list above, the numbers for CPOD1 will be shown below." + }, + "location": "main", + "parent": "" + }, + { + "id": "4aaf08f5-9997-47ed-95ff-c3e8f96382c1", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "CPOD1", + "inputName": "POD_NAME", + "inputSource": "devices", + "selectedCustomTags": [], + "tagLabel": "pod_name", + "tags": "" + }, + "location": "inputs", + "parent": "" + } + ], + "lastUpdated": 1691171503238, + "lastUpdatedBy": "tamas" + } + ] +} \ No newline at end of file diff --git a/_downloads/c6f35091ac00f81b13d1b824db7607b2/count_if_nz_out_or_in_trfk.json b/_downloads/c6f35091ac00f81b13d1b824db7607b2/count_if_nz_out_or_in_trfk.json new file mode 100644 index 0000000..4977a54 --- /dev/null +++ b/_downloads/c6f35091ac00f81b13d1b824db7607b2/count_if_nz_out_or_in_trfk.json @@ -0,0 +1,42 @@ +{ + "dashboards": [ + { + "key": "e25ce3b5-240c-4ba2-a7e6-2ce46865bf7c", + "createdAt": [ + 57271534, + 1561 + ], + "createdBy": "cvpadmin", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Count interfaces with non-zero outbound OR inbound traffic (with key existence check)", + "description": "", + "widgets": [ + { + "id": "67d75b07-a576-45f1-97f5-219f431bb585", + "name": "Count interfaces with non-zero outbound OR inbound traffic", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 8, + "height": 10 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "# Get the latest rates value for all interfaces in the network using widcards and apply merge on the inner timeseries\nlet data = `analytics:/Devices/*/versioned-data/interfaces/data/*/rates`\n\n# keep only entries where the outOctets and inOctets field is present and non-zero\nlet latestRates = data | map(_value | where(dictHasKey(merge(_value), \"inOctets\") && merge(_value)[\"inOctets\"] > 0 \\\n || dictHasKey(merge(_value), \"outOctets\") && merge(_value)[\"outOctets\"] > 0))\n\n# count the remaining interfaces (with outbound traffic) for each device and compute the sum to get the number of active interfaces in the entire network\nsum(latestRates | map(length(_value)))", + "visualization": "singleValue" + }, + "location": "main" + } + ], + "lastUpdated": 1688672722533, + "lastUpdatedBy": "cvpadmin" + } + ] +} \ No newline at end of file diff --git a/_downloads/c82a99a56d60430b05acd04ea60030aa/igmp_snooping.json b/_downloads/c82a99a56d60430b05acd04ea60030aa/igmp_snooping.json new file mode 100644 index 0000000..78e83b8 --- /dev/null +++ b/_downloads/c82a99a56d60430b05acd04ea60030aa/igmp_snooping.json @@ -0,0 +1,88 @@ +{ + "dashboards": [ + { + "key": "cdfff4f9-a6a1-4190-b635-2e717811aa28", + "createdAt": [ + 999819416, + 1574 + ], + "createdBy": "cvpadmin", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "IGMP Snooping", + "description": "#device", + "widgets": [ + { + "id": "dc092a39-346a-4c47-9b3f-c9e3cdf97ecd", + "name": "Snooping Groups", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 11, + "height": 21 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let igmpSnooping = `<_device>:/Sysdb/bridging/igmpsnooping/forwarding/status/vlanStatus/*/ethGroup/*/intf`\nlet filteredIgmpSnooping = igmpSnooping | recmap(2, merge(_value))\nlet result = newDict()\nfor vlanKey, macAddrIntf in filteredIgmpSnooping {\n for macAddr, intfState in macAddrIntf {\n # Create a list of interfaces a vlan is mapped to\n let interfaceList = \"\"\n if length(intfState) > 1{\n for interface, intfBool in intfState {\n let interfaceList = interfaceList + str(interface) + \", \"\n }\n } else {\n # if there is only 1 interface no need to add a comma\n let interfaceList = str(dictKeys(intfState)[0])\n }\n let vlan = reFindCaptures(str(vlanKey), \"{\\\"value\\\":(\\d+)}\")[0][1]\n result[macAddr] = newDict() | setFields(\"VLAN\", vlan, \"Members\", interfaceList)\n }\n}\nresult", + "graphConfig": { + "defaultSort": { + "key": "Switch" + }, + "columns": { + "Switch": { + "mapToHostname": true + } + }, + "columnOrders": { + "key": 1, + "Switch": 2, + "Interface": 3, + "Status": 4, + "Switchport Mode": 5, + "Trunked VLANs": 6, + "Access VLAN": 7, + "Port Description": 8, + "Chassis Identifier": 9, + "Remote LLDP Hostname": 10, + "Remote LLDP PortID": 11, + "Speed": 12 + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "eaf7b6ec-8b70-4970-aa3d-f7bf1760c931", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "BAD032986065E8DC14CBB6472EC314A6", + "inputName": "device", + "inputSource": "devices", + "tagLabel": "device" + }, + "location": "inputs", + "parent": "" + } + ], + "lastUpdated": 1691178747788, + "lastUpdatedBy": "cvpadmin" + } + ] +} \ No newline at end of file diff --git a/_downloads/c934687ff9ecb394044a37b78a0db889/eol_planning.json b/_downloads/c934687ff9ecb394044a37b78a0db889/eol_planning.json new file mode 100644 index 0000000..c5bd7b6 --- /dev/null +++ b/_downloads/c934687ff9ecb394044a37b78a0db889/eol_planning.json @@ -0,0 +1,57 @@ +{ + "dashboards": [ + { + "key": "0800417e-e4be-41c0-a4a7-c5e8fd033c6e", + "createdAt": [ + 952882732, + 1574 + ], + "createdBy": "cvpadmin", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "EoL Planning Table", + "description": "", + "widgets": [ + { + "id": "9b129c35-a1ff-4951-8c1a-e3f144fa359d", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 18, + "height": 25 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let inventory = `analytics:/DatasetInfo/Devices`\nlet hardware = merge(`analytics:/lifecycles/hardware`)\nlet softwareLife = merge(`analytics:/lifecycles/software`)\nlet software = merge(`analytics:/lifecycles/devices/software`)\nlet skus = merge(`analytics:/BugAlerts/skus`)\n\nlet filteredSkus = newDict()\nfor key, val in skus {\n if strContains(key, \"DCS-\") {\n filteredSkus[key] = newDict()\n let relNum = \"\"\n if str(val[\"releaseDeprecated\"]) != \"[]\" {\n let deprecatedReleaseNum = strSplit(str(val[\"releaseDeprecated\"]),\",\")[0]\n let deprecatedReleaseNum = strReplace(deprecatedReleaseNum, \"[\",\"\")\n let deprecatedReleaseNum = strReplace(deprecatedReleaseNum, \"]\",\"\")\n let deprecatedReleaseNum = strReplace(deprecatedReleaseNum, \"\\\"\",\"\")\n let deprecatedReleaseNum = strSplit(deprecatedReleaseNum,\".\")\n let relNum = deprecatedReleaseNum[0]+\".\"+str(num(deprecatedReleaseNum[1])-1)\n }\n filteredSkus[key][\"releaseDeprecated\"] = relNum\n }\n}\nlet inv = newDict()\nfor deviceUpdate, device in inventory {\n for deviceSN, deviceData in device {\n inv[deviceSN] = newDict()\n inv[deviceSN][\"Hostname\"] = deviceData[\"hostname\"]\n inv[deviceSN][\"ModelName\"] = deviceData[\"modelName\"]\n inv[deviceSN][\"Version\"] = deviceData[\"eosVersion\"]\n inv[deviceSN][\"TerminAttr\"] = deviceData[\"terminAttrVersion\"]\n for hw, hwEol in hardware {\n if deviceData[\"modelName\"] == hw {\n inv[deviceSN][\"Hardware EndOfSale\"] = hwEol[\"endOfSale\"]\n inv[deviceSN][\"Hardware EndOfTACSupport\"] = hwEol[\"endOfTACSupport\"]\n inv[deviceSN][\"Hardware EndOfRMARequests\"] = hwEol[\"endOfHardwareRMARequests\"]\n inv[deviceSN][\"Hardware EndOfLife\"] = hwEol[\"endOfLife\"]\n }\n }\n for switch, switchEol in software {\n if switch == deviceSN {\n inv[deviceSN][\"Current Software EndOfLife\"] = switchEol[\"endOfSupport\"]\n }\n }\n for skuKey, skuVal in filteredSkus {\n if strContains(skuKey, deviceData[\"modelName\"]) {\n inv[deviceSN][\"Last Supported Software Train\"] = skuVal[\"releaseDeprecated\"]\n }\n }\n for sw, swEol in softwareLife {\n if dictHasKey(inv[deviceSN],\"Last Supported Software Train\") && sw == inv[deviceSN][\"Last Supported Software Train\"] {\n inv[deviceSN][\"Last Supported Software Train EndOfLife\"] = swEol[\"endOfSupport\"]\n }\n }\n }\n}\ninv\n", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true, + "columnTitle": "Hostname" + } + } + }, + "visualization": "table" + }, + "location": "main", + "styles": { + "hideTitle": true, + "backgroundColor": "", + "hideHorizontalBar": false, + "titleSize": 14 + }, + "parent": "" + } + ], + "lastUpdated": 1691026823906, + "lastUpdatedBy": "cvpadmin" + } + ] +} \ No newline at end of file diff --git a/_downloads/cbae5eb3c3a343d03f4467e46c657e00/count_if_nz_out_trfk.json b/_downloads/cbae5eb3c3a343d03f4467e46c657e00/count_if_nz_out_trfk.json new file mode 100644 index 0000000..9693b75 --- /dev/null +++ b/_downloads/cbae5eb3c3a343d03f4467e46c657e00/count_if_nz_out_trfk.json @@ -0,0 +1,42 @@ +{ + "dashboards": [ + { + "key": "353286fb-60d1-4323-ac83-b1000e1befb2", + "createdAt": [ + 55723152, + 1561 + ], + "createdBy": "cvpadmin", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Count interfaces with non-zero outbound traffic", + "description": "", + "widgets": [ + { + "id": "215f6abc-e883-412a-9aba-4c1b3b67441a", + "name": "Count interfaces with non-zero outbound traffic", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 6, + "height": 7 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "# Get the latest rates value for all interfaces in the network using widcards and apply merge on the inner timeseries\nlet data = `analytics:/Devices/*/versioned-data/interfaces/data/*/aggregate/rates/15m` | recmap(2, merge(_value))\n\n# keep only entries where the outOctets field is present and avg is non-zero\nlet latestRatesByInterface = data | map(_value | where(dictHasKey(_value, \"outOctets\") && _value[\"outOctets\"][\"avg\"] > 0))\n\n#count the remaining interfaces (with outbound traffic) for each device and compute the sum to get the number of active interfaces in the entire network\nsum(latestRatesByInterface | map(length(_value)))", + "visualization": "singleValue" + }, + "location": "main" + } + ], + "lastUpdated": 1688671194684, + "lastUpdatedBy": "cvpadmin" + } + ] +} \ No newline at end of file diff --git a/_downloads/d2e3dbcd040aaaa58db8d0e970a7c602/ntp_stats.json b/_downloads/d2e3dbcd040aaaa58db8d0e970a7c602/ntp_stats.json new file mode 100644 index 0000000..5f10a4b --- /dev/null +++ b/_downloads/d2e3dbcd040aaaa58db8d0e970a7c602/ntp_stats.json @@ -0,0 +1,55 @@ +{ + "dashboards": [ + { + "key": "c4bd6309-9f04-4075-9009-76f2a4d6c2d0", + "createdAt": [ + 805090024, + 1564 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "NTP stats", + "description": "", + "widgets": [ + { + "id": "a9e9d5d3-e67a-4aa3-931b-a8847a72aa8c", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 8, + "height": 11 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let ntpData = `*:/NTP/status/system/variables` | map(merge(_value))\nlet output = newDict()\nfor devID,devData in ntpData{\n output[devID] = newDict()\n output[devID][\"peer\"]= devData[\"refid\"]\n output[devID][\"Stratum\"]=devData[\"stratum\"]\n output[devID][\"OffSet\"]=devData[\"offset\"]\n }\noutput\n", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true + } + } + }, + "visualization": "table" + }, + "location": "main", + "styles": { + "hideTitle": true, + "backgroundColor": "", + "hideHorizontalBar": false, + "titleSize": 14 + } + } + ], + "lastUpdated": 1680137491777, + "lastUpdatedBy": "tamas" + } + ] + } diff --git a/_downloads/dc567b8a4d6dcaf4edae353b23c42975/lanz_queue_size.json b/_downloads/dc567b8a4d6dcaf4edae353b23c42975/lanz_queue_size.json new file mode 100644 index 0000000..efbde3c --- /dev/null +++ b/_downloads/dc567b8a4d6dcaf4edae353b23c42975/lanz_queue_size.json @@ -0,0 +1,101 @@ +{ + "dashboards": [ + { + "key": "b049996e-78e3-4e02-9b04-30288b6e2eec", + "createdAt": [ + 60975042, + 1553 + ], + "createdBy": "marissa", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "LANZ", + "description": "", + "widgets": [ + { + "id": "f90bae68-3ad1-49b8-8323-1f107677019a", + "name": "LANZ Queue Length", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 16, + "height": 18 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "# Section to get a Serial-number to hostname dict (SNToHostnameDict)\nlet SNToHostnameDict = newDict()\nlet dataDeviceAnalytics = `analytics:/Devices/*/versioned-data/Device`\nfor device, deviceData in dataDeviceAnalytics {\n let mergedDeviceData = merge(deviceData)\n if (dictHasKey(mergedDeviceData, \"hostname\")) {\n SNToHostnameDict[device] = mergedDeviceData[\"hostname\"]\n }\n}\n\n\nlet data = `analytics:/Devices/*/versioned-data/interfaces/data/*/lanz/aggregate-congestion/*`[24h]\nlet result = newDict()\nfor deviceSN, deviceData in data {\n for interface, interfaceData in deviceData {\n for intervalKey, aggData in interfaceData {\n let empty = true\n for timestamp, timeseriesData in aggData {\n if (dictHasKey(timeseriesData, _queue) && timeseriesData[_queue][\"avg\"] > 0 && intervalKey == _interval && strHasPrefix(interface, \"Ethernet\")) {\n let empty = false\n }\n }\n if (!empty) {\n let deviceHostname = SNToHostnameDict[deviceSN]\n result[interface + \" on \" + deviceHostname + \" (\" + intervalKey + \")\"] = aggData | map(_value[_queue][\"avg\"])\n }\n \n }\n }\n}\n\nresult", + "graphConfig": { + "mapToHostname": true + }, + "visualization": "horizonGraph" + }, + "location": "main" + }, + { + "id": "d9fc8dec-a374-415e-9562-d7ca6f4a0819", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 6, + "height": 2 + }, + "type": "variable-widget", + "inputs": { + "defaultValue": "queueSize", + "inputName": "queue", + "inputType": "SingleSelect", + "inputWidgetId": "d9fc8dec-a374-415e-9562-d7ca6f4a0819", + "selectData": { + "manualOptions": [ + "queueSize", + "qDropCount", + "txLatency" + ] + }, + "variableType": "String" + }, + "location": "inputs" + }, + { + "id": "1b50cb61-3a1d-4ca8-bd3a-81839a8ef972", + "name": "", + "position": { + "x": 6, + "y": 0 + }, + "dimensions": { + "width": 6, + "height": 2 + }, + "type": "variable-widget", + "inputs": { + "defaultValue": "10s", + "inputName": "interval", + "inputType": "SingleSelect", + "inputWidgetId": "1b50cb61-3a1d-4ca8-bd3a-81839a8ef972", + "selectData": { + "manualOptions": [ + "10s", + "1m", + "1ms" + ] + }, + "variableType": "String" + }, + "location": "inputs" + } + ], + "lastUpdated": 1667932191812, + "lastUpdatedBy": "marissa" + } + ] +} \ No newline at end of file diff --git a/_downloads/de6f3f61da31241148847b673339120f/varcore.json b/_downloads/de6f3f61da31241148847b673339120f/varcore.json new file mode 100644 index 0000000..16d8606 --- /dev/null +++ b/_downloads/de6f3f61da31241148847b673339120f/varcore.json @@ -0,0 +1,61 @@ +{ + "dashboards": [ + { + "key": "5e5cc5e0-17b1-48de-be12-29239f7254b9", + "createdAt": [ + 1033268786, + 1574 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "/var/core monitoring", + "description": "", + "widgets": [ + { + "id": "42969bdc-6c44-422a-ab0a-6e11235cf6b7", + "name": "Devices with files in /var/core", + "position": { + "x": 0, + "y": 3 + }, + "dimensions": { + "width": 9, + "height": 8 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = `analytics:/Devices/*/versioned-data/Device` | map(merge(_value)[\"hostname\"])\nlet result = newDict()\nlet deviceDiskStats = `*:/Kernel/vfs/stat/\\/var\\/core` | map(merge(_value))\nlet deviceDiskStats = deviceDiskStats | where(dictHasKey(_value, \"blocks\") && dictHasKey(_value, \"bfree\") && _value[\"blocks\"] != _value[\"bfree\"])\nfor device, diskStat in deviceDiskStats {\n result[devices[device]] = newDict() | setFields(\"value\", true)\n}\nresult\n", + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "3c38bfbe-e0bd-4786-a425-404cc54a2cfa", + "name": "Note", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 9, + "height": 3 + }, + "type": "text-widget", + "inputs": { + "textContent": "### The following devices have at least 1 file in `/var/core` directory which may be an indicative of agents that have been crashing" + }, + "location": "main", + "parent": "" + } + ], + "lastUpdated": 1691102940195, + "lastUpdatedBy": "tamas" + } + ] +} \ No newline at end of file diff --git a/_downloads/ef1903d014a307d4fd3df053682dc335/capacity_planning_routing_switching.json b/_downloads/ef1903d014a307d4fd3df053682dc335/capacity_planning_routing_switching.json new file mode 100644 index 0000000..ff9981b --- /dev/null +++ b/_downloads/ef1903d014a307d4fd3df053682dc335/capacity_planning_routing_switching.json @@ -0,0 +1,528 @@ +{ + "dashboards": [ + { + "key": "fa602b02-6d82-4744-b8e1-d65cf96109ac", + "createdAt": [ + 825221310, + 1574 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Capacity Planning Routing and Switching", + "description": "", + "widgets": [ + { + "id": "b1f59da6-eadb-4bd8-a046-25a7088c7c42", + "name": "Low Density Leaf Switches Numbers (7020R) Tagged with the LDL Label", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 11, + "height": 10 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\r\nlet devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/LDL/elements`)\r\n\r\nif str(_POD_NAME) == \"\" {\r\n let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(length(_value) == 0 ? 0 : _value) \r\n let arpCount = `*:/Smash/arp/status/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n} else {\r\n let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(length(_value) == 0 ? 0 : _value) \r\n let arpCount = `*:/Smash/arp/status/_counts` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n}\r\n\r\nlet smash = macStatusCount | map(merge(_value) | fields(\"smashFdbStatus\"))\r\nlet nbvlan = macConfigCount | map(merge(_value) | fields(\"vlanConfig\"))\r\nlet nbVRF = vrfCount | map(length(merge(_value)))\r\nlet nbARP = arpCount | map(merge(_value))\r\n\r\nfor deviceKey, deviceValue in nbVRF {\r\n let nbr = newDict()\r\n nbr[\"VRF number\"] = deviceValue\r\n nbVRF[deviceKey] = nbr\r\n}\r\n\r\nfor devicekey, devicevalue in smash {\r\n smash[devicekey][\"VLAN count\"] = nbvlan[devicekey][\"vlanConfig\"]\r\n smash[devicekey][\"VRF count\"] = dictHasKey(nbVRF, devicekey)? nbVRF[devicekey][\"VRF number\"] : 0 \r\n smash[devicekey][\"ARP count\"] = nbARP[devicekey][\"arpEntry\"]\r\n smash[devicekey][\"ND count\"] = nbARP[devicekey][\"neighborEntry\"]\r\n}\r\n\r\nsmash | map(_value | renameFields(\"smashFdbStatus\",\"MAC count\"))", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true, + "columnTitle": "Device" + }, + "MAC count": { + "type": "number", + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 200000, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 200000, + "to": 245000, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 245000, + "result": { + "color": "red", + "index": 2 + } + } + } + ], + "showDotIndicator": true + }, + "VLAN count": { + "type": "number", + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 75, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 75, + "to": 99, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 100, + "result": { + "color": "red", + "index": 2 + } + } + } + ], + "showDotIndicator": true + }, + "VRF count": { + "type": "number", + "showDotIndicator": true, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 4, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 4, + "to": 5, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 5, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "eae97ea6-d545-4d8b-a1f2-f283142d80bb", + "name": "High Density Leaf Switches Numbers (7050X3) Tagged with HDL Label", + "position": { + "x": 0, + "y": 10 + }, + "dimensions": { + "width": 11, + "height": 10 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\r\nlet devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/HDL/elements`)\r\n\r\nif str(_POD_NAME) == \"\" {\r\n let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(length(_value) == 0 ? 0 : _value)\r\n let arpCount = `*:/Smash/arp/status/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n} else {\r\n let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(length(_value) == 0 ? 0 : _value)\r\n let arpCount = `*:/Smash/arp/status/_counts` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n}\r\n\r\nlet smash = macStatusCount | map(merge(_value) | fields(\"smashFdbStatus\"))\r\nlet nbvlan = macConfigCount | map(merge(_value) | fields(\"vlanConfig\"))\r\nlet nbVRF = vrfCount | map(length(merge(_value)))\r\nlet nbARP = arpCount | map(merge(_value))\r\n\r\nfor deviceKey, deviceValue in nbVRF {\r\n let nbr = newDict()\r\n nbr[\"VRF number\"]=deviceValue\r\n nbVRF[deviceKey]= nbr\r\n}\r\n\r\nfor devicekey, devicevalue in smash {\r\n smash[devicekey][\"VLAN count\"]=nbvlan[devicekey][\"vlanConfig\"]\r\n smash[devicekey][\"VRF count\"]=nbVRF[devicekey][\"VRF number\"]\r\n smash[devicekey][\"ARP count\"]=nbARP[devicekey][\"arpEntry\"]\r\n smash[devicekey][\"ND count\"]=nbARP[devicekey][\"neighborEntry\"]\r\n}\r\n\r\nsmash | map(_value | renameFields(\"smashFdbStatus\",\"MAC count\"))", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true, + "columnTitle": "Device" + }, + "VLAN count": { + "type": "number", + "showDotIndicator": true, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 500, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 500, + "to": 549, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 550, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "MAC count": { + "type": "number", + "showDotIndicator": true, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 95000, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 95000, + "to": 96000, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 96000, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "VRF count": { + "type": "number", + "showDotIndicator": true, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 4, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 4, + "to": 5, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 5, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "f3177269-8560-49a5-8cac-7a189b534993", + "name": "EVPN Gateways Numbers (7280R2)", + "position": { + "x": 11, + "y": 0 + }, + "dimensions": { + "width": 11, + "height": 10 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)\r\nlet devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/EGW/elements`)\r\n\r\nif str(_POD_NAME) == \"\" {\r\n let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(length(_value) == 0 ? 0 : _value)\r\n let arpCount = `*:/Smash/arp/status/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n} else {\r\n let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\"))) | map(length(_value) == 0 ? 0 : _value)\r\n let arpCount = `*:/Smash/arp/status/_counts` | where(dictHasKey(devices, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")) && dictHasKey(devicesSwitchLabel, complexKey(\"{\\\"deviceID\\\": \\\"\"+_key+\"\\\"}\")))\r\n}\r\n\r\nlet smash = macStatusCount | map(merge(_value) | fields(\"smashFdbStatus\"))\r\nlet nbvlan = macConfigCount | map(merge(_value) | fields(\"vlanConfig\"))\r\nlet nbVRF = vrfCount | map(length(merge(_value)))\r\nlet nbARP = arpCount | map(merge(_value))\r\n\r\nfor deviceKey, deviceValue in nbVRF {\r\n let nbr = newDict()\r\n nbr[\"VRF number\"]=deviceValue\r\n nbVRF[deviceKey]= nbr\r\n}\r\n\r\nfor devicekey, devicevalue in smash {\r\n smash[devicekey][\"VLAN count\"]=nbvlan[devicekey][\"vlanConfig\"]\r\n smash[devicekey][\"VRF count\"]=nbVRF[devicekey][\"VRF number\"]\r\n smash[devicekey][\"ARP count\"]=nbARP[devicekey][\"arpEntry\"]\r\n smash[devicekey][\"ND count\"]=nbARP[devicekey][\"neighborEntry\"]\r\n}\r\n\r\nsmash | map(_value | renameFields(\"smashFdbStatus\",\"MAC count\"))", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true, + "columnTitle": "Device" + }, + "VRF count": { + "type": "number", + "showDotIndicator": true, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 9, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 9, + "to": 10, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 10, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + }, + "MAC count": { + "type": "number", + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 760000, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 760000, + "to": 768000, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 768000, + "result": { + "color": "red", + "index": 2 + } + } + } + ], + "showDotIndicator": true + }, + "VLAN count": { + "type": "number", + "showDotIndicator": true, + "colorMappings": [ + { + "type": "range", + "options": { + "from": 0, + "to": 980, + "result": { + "color": "green", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 980, + "to": 1099, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 1100, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + } + } + }, + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "fcd8e3de-19cb-4960-b2ab-56cd51d42940", + "name": "IPv4/IPv6 Routes Count", + "position": { + "x": 11, + "y": 10 + }, + "dimensions": { + "width": 11, + "height": 10 + }, + "type": "metrics-widget-table", + "inputs": { + "components": [], + "isTokenSearchEnabled": true, + "metricKeys": [ + "V4_TOTAL_ROUTES_TABLE_SIZE", + "V6_TOTAL_ROUTES_TABLE_SIZE" + ], + "metricSource": "devices", + "selectedCustomTags": [ + "POD_NAME" + ], + "tags": "", + "viewType": "metric" + }, + "location": "main", + "parent": "" + }, + { + "id": "f6947f86-4899-4662-a67a-05344ab7dd44", + "name": "VXLAN/VNI Numbers", + "position": { + "x": 0, + "y": 20 + }, + "dimensions": { + "width": 11, + "height": 10 + }, + "type": "metrics-widget-table", + "inputs": { + "components": [], + "isTokenSearchEnabled": true, + "metricKeys": [ + "VXLAN_REMOTE_MACS_COUNT", + "VXLAN_VNIS_COUNT" + ], + "metricSource": "devices", + "selectedCustomTags": [ + "POD_NAME" + ], + "tags": "", + "viewType": "metric" + }, + "location": "main", + "parent": "" + }, + { + "id": "53adc4af-13da-43a5-9280-0417012b00eb", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "", + "inputName": "POD_NAME", + "inputSource": "devices", + "selectedCustomTags": [], + "tagLabel": "pod_name", + "tags": "" + }, + "location": "inputs", + "parent": "" + } + ], + "lastUpdated": 1690981121727, + "lastUpdatedBy": "tamas" + } + ] +} \ No newline at end of file diff --git a/_downloads/f3395aa3301581323945f7f6d6c2c695/intf_counter_rate_sum_per_dev.json b/_downloads/f3395aa3301581323945f7f6d6c2c695/intf_counter_rate_sum_per_dev.json new file mode 100644 index 0000000..e178c17 --- /dev/null +++ b/_downloads/f3395aa3301581323945f7f6d6c2c695/intf_counter_rate_sum_per_dev.json @@ -0,0 +1,96 @@ +{ + "dashboards": [ + { + "key": "a064e1b3-8158-4a3e-b7d5-5d2789eb6320", + "createdAt": [ + 781166221, + 1581 + ], + "createdBy": "tamas", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Interface counters per interface list per device (aql)", + "description": "", + "widgets": [ + { + "id": "c8042099-a939-4f4d-b5af-8c8bfea7b4bf", + "name": "Total In BW", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 14, + "height": 13 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "let intfRatesIn =`analytics:/Devices/<_device>/versioned-data/interfaces/data/*/aggregate/rates/1m`[_timeWindowStart:_timeWindowEnd] | map(_value | field(\"inOctets\") | field(\"avg\"))\nlet intfRatesOut =`analytics:/Devices/<_device>/versioned-data/interfaces/data/*/aggregate/rates/1m`[_timeWindowStart:_timeWindowEnd] | map(_value | field(\"outOctets\") | field(\"avg\"))\nlet intfRatesInFiltered = newDict()\nlet intfRatesOutFiltered = newDict()\nlet res = newDict()\n\nfor intfKey, intfVal in intfRatesIn{\n if dictHasKey(_interfaces, intfKey){\n intfRatesInFiltered[intfKey] = intfVal\n }\n}\n\nfor intfKey, intfVal in intfRatesOut{\n if dictHasKey(_interfaces, intfKey){\n intfRatesOutFiltered[intfKey] = intfVal\n }\n}\n\nlet sumInOctets = aggregate(intfRatesInFiltered | map(_value | resample(10s)), \"sum\")\nlet sumOutOctets = aggregate(intfRatesOutFiltered | map(_value | resample(10s)), \"sum\")\nnewDict() | setFields(_device + \" inOctets rates\", sumInOctets/125000, _device + \" outOctets rates\", sumOutOctets/125000) \n", + "graphConfig": { + "mapToHostname": true + }, + "visualization": "horizonGraph" + }, + "location": "main" + }, + { + "id": "dfc6c06d-9588-4d50-8dc0-a4ed71cb9f26", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "0123F2E4462997EB155B7C50EC148767", + "inputName": "device", + "inputSource": "devices", + "inputWidgetId": "dfc6c06d-9588-4d50-8dc0-a4ed71cb9f26", + "tagLabel": "device" + }, + "location": "inputs" + }, + { + "id": "7fd6da21-f0ab-4e02-be44-bf1068f291a4", + "name": "", + "position": { + "x": 4, + "y": 0 + }, + "dimensions": { + "width": 6, + "height": 2 + }, + "type": "variable-widget", + "inputs": { + "defaultValue": [ + "Ethernet1", + "Ethernet2" + ], + "inputName": "interfaces", + "inputType": "MultiSelect", + "selectData": { + "manualOptions": [ + "let data =" + ], + "createOptionsUsingAql": true, + "query": "let data = `analytics:/Devices/<_device>/versioned-data/interfaces/data/*`\ndata" + }, + "variableType": "String" + }, + "location": "inputs" + } + ], + "lastUpdated": 1698367019518, + "lastUpdatedBy": "tamas" + } + ] + } \ No newline at end of file diff --git a/_downloads/f970ca60c945732db5df73f774ea6385/port_utilization.json b/_downloads/f970ca60c945732db5df73f774ea6385/port_utilization.json new file mode 100644 index 0000000..9e46825 --- /dev/null +++ b/_downloads/f970ca60c945732db5df73f774ea6385/port_utilization.json @@ -0,0 +1,134 @@ +{ + "dashboards": [ + { + "key": "84676f53-d1aa-434d-b1b4-d89332f22f9f", + "createdAt": [ + 782155240, + 1574 + ], + "createdBy": "cvpadmin", + "metaData": { + "schemaVersion": "3", + "legacyKey": "", + "legacyVersion": "", + "fromPackage": "" + }, + "name": "Port Utilization", + "description": "", + "widgets": [ + { + "id": "99f5d7c5-b1db-4c3b-afd0-91a692137188", + "name": "Port Utilization", + "position": { + "x": 0, + "y": 4 + }, + "dimensions": { + "width": 17, + "height": 12 + }, + "type": "aql-query-widget", + "inputs": { + "expression": "# Get devices with tag label \"switch_role\" and default value of \"hdl\".\n# To choose another tag value, select from the input's dropdown.\n\n# Load all interface configurations\nlet devs = merge(`analytics:/tags/labels/devices/switch_role/value/<_switch_role>/elements`)\nlet devices = newDict()\n\nfor deviceIDstr, val in devs {\n let devId = strSplit(str(deviceIDstr), \":\")[1]\n let devId = strReplace(devId,\"\\\"\", \"\")\n let devId = strReplace(devId, \"}\", \"\")\n devices[devId] = val\n}\n\nlet data = `*:/Sysdb/interface/config/eth/phy/slice/1/intfConfig/*` | recmap(2,merge(_value) | fields(\"description\")) | map(_value | where(strContains(_key, \"Ethernet\")))| where( dictHasKey(devices, _key))\nlet result = `*:/Sysdb/interface/config/eth/phy/slice/1/intfConfig/*` | recmap(2,merge(_value) | fields(\"description\")) | map(_value | where(strContains(_key, \"Ethernet\")))| where( dictHasKey(devices, _key))\n\nfor deviceKey, deviceValue in data {\n let nbPorts = 0\n let nbUnusedPorts = 0\n for portKey, portValue in deviceValue {\n if (portValue[\"description\"]==\"UNUSED\"){\n let nbUnusedPorts = nbUnusedPorts+1\n } \n let nbPorts = nbPorts+1\n result[deviceKey][\"Number of Unused Ports\"] = nbUnusedPorts\n result[deviceKey][\"Number of Ports\"] = nbPorts\n if (nbPorts != 0){\n result[deviceKey][\"Port free %\"] = nbUnusedPorts / nbPorts * 100\n } else {\n result[deviceKey][\"Port free %\"] = \"N/A\"\n }\n }\n}\nresult | map(_value | fields(\"Number of Unused Ports\",\"Number of Ports\",\"Port free %\"))", + "graphConfig": { + "columns": { + "key": { + "mapToHostname": true + }, + "Port free %": { + "unit": "%", + "colorMappings": [ + { + "type": "range", + "options": { + "from": 25, + "to": 100, + "result": { + "color": "green9", + "index": 0 + } + } + }, + { + "type": "range", + "options": { + "from": 15, + "to": 25, + "result": { + "color": "yellow", + "index": 1 + } + } + }, + { + "type": "range", + "options": { + "from": 0, + "to": 15, + "result": { + "color": "red", + "index": 2 + } + } + } + ] + } + } + }, + "mode": "standard", + "visualization": "table" + }, + "location": "main", + "parent": "" + }, + { + "id": "95511a7b-1d02-4485-aa41-dcc153b01f23", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 17, + "height": 4 + }, + "type": "text-widget", + "inputs": { + "textContent": "# Port Utilization\n\nThe count of the ports is based on the port description.\nAll unused ports have the \"UNUSED\" description." + }, + "location": "main", + "styles": { + "hideTitle": true, + "backgroundColor": "", + "hideHorizontalBar": false, + "titleSize": 14 + }, + "parent": "" + }, + { + "id": "96f80c5b-97b9-47c1-9fd5-fbb748ac3072", + "name": "", + "position": { + "x": 0, + "y": 0 + }, + "dimensions": { + "width": 4, + "height": 2 + }, + "type": "input-widget", + "inputs": { + "defaultValue": "hdl", + "inputName": "switch_role", + "inputSource": "devices", + "tagLabel": "switch_role" + }, + "location": "inputs", + "parent": "" + } + ], + "lastUpdated": 1690887530533, + "lastUpdatedBy": "cvpadmin" + } + ] +} \ No newline at end of file diff --git a/_images/abootfn44.png b/_images/abootfn44.png new file mode 100644 index 0000000..3f69185 Binary files /dev/null and b/_images/abootfn44.png differ diff --git a/_images/admin_state.png b/_images/admin_state.png new file mode 100644 index 0000000..554dca7 Binary files /dev/null and b/_images/admin_state.png differ diff --git a/_images/arp_entries.png b/_images/arp_entries.png new file mode 100644 index 0000000..ad37c1f Binary files /dev/null and b/_images/arp_entries.png differ diff --git a/_images/bgp_states.png b/_images/bgp_states.png new file mode 100644 index 0000000..27c5695 Binary files /dev/null and b/_images/bgp_states.png differ diff --git a/_images/count_if_nz_out_or_in_trfk.png b/_images/count_if_nz_out_or_in_trfk.png new file mode 100644 index 0000000..9d9301a Binary files /dev/null and b/_images/count_if_nz_out_or_in_trfk.png differ diff --git a/_images/count_if_nz_out_trfk.png b/_images/count_if_nz_out_trfk.png new file mode 100644 index 0000000..79ce410 Binary files /dev/null and b/_images/count_if_nz_out_trfk.png differ diff --git a/_images/eol_planning.png b/_images/eol_planning.png new file mode 100644 index 0000000..07fff3d Binary files /dev/null and b/_images/eol_planning.png differ diff --git a/_images/fn72.png b/_images/fn72.png new file mode 100644 index 0000000..a0df837 Binary files /dev/null and b/_images/fn72.png differ diff --git a/_images/hardware_health_check.png b/_images/hardware_health_check.png new file mode 100644 index 0000000..a069909 Binary files /dev/null and b/_images/hardware_health_check.png differ diff --git a/_images/if_info_filter_port_desc.png b/_images/if_info_filter_port_desc.png new file mode 100644 index 0000000..00acecd Binary files /dev/null and b/_images/if_info_filter_port_desc.png differ diff --git a/_images/igmp_snooping.png b/_images/igmp_snooping.png new file mode 100644 index 0000000..64c4d99 Binary files /dev/null and b/_images/igmp_snooping.png differ diff --git a/_images/important_pod_count.png b/_images/important_pod_count.png new file mode 100644 index 0000000..4cc5211 Binary files /dev/null and b/_images/important_pod_count.png differ diff --git a/_images/intf_counter_rate_sum_per_dev.png b/_images/intf_counter_rate_sum_per_dev.png new file mode 100644 index 0000000..f2397cb Binary files /dev/null and b/_images/intf_counter_rate_sum_per_dev.png differ diff --git a/_images/lanz_txlatency.png b/_images/lanz_txlatency.png new file mode 100644 index 0000000..85f30ba Binary files /dev/null and b/_images/lanz_txlatency.png differ diff --git a/_images/mac_entries.png b/_images/mac_entries.png new file mode 100644 index 0000000..411351d Binary files /dev/null and b/_images/mac_entries.png differ diff --git a/_images/macs_per_device.png b/_images/macs_per_device.png new file mode 100644 index 0000000..2ce1030 Binary files /dev/null and b/_images/macs_per_device.png differ diff --git a/_images/ntp_stats.png b/_images/ntp_stats.png new file mode 100644 index 0000000..ae6ff44 Binary files /dev/null and b/_images/ntp_stats.png differ diff --git a/_images/output_power_over_48h.png b/_images/output_power_over_48h.png new file mode 100644 index 0000000..c663070 Binary files /dev/null and b/_images/output_power_over_48h.png differ diff --git a/_images/port_utilization.png b/_images/port_utilization.png new file mode 100644 index 0000000..beed0e7 Binary files /dev/null and b/_images/port_utilization.png differ diff --git a/_images/system_health_check1.png b/_images/system_health_check1.png new file mode 100644 index 0000000..e0db35c Binary files /dev/null and b/_images/system_health_check1.png differ diff --git a/_images/system_health_check2.png b/_images/system_health_check2.png new file mode 100644 index 0000000..73888bc Binary files /dev/null and b/_images/system_health_check2.png differ diff --git a/_images/tcam_capacity.png b/_images/tcam_capacity.png new file mode 100644 index 0000000..798dfbc Binary files /dev/null and b/_images/tcam_capacity.png differ diff --git a/_images/varcore.png b/_images/varcore.png new file mode 100644 index 0000000..65ed293 Binary files /dev/null and b/_images/varcore.png differ diff --git a/_images/vrfs_in_vlans.png b/_images/vrfs_in_vlans.png new file mode 100644 index 0000000..1c690cb Binary files /dev/null and b/_images/vrfs_in_vlans.png differ diff --git a/_images/webinar03_bgp1.png b/_images/webinar03_bgp1.png new file mode 100644 index 0000000..cd709dd Binary files /dev/null and b/_images/webinar03_bgp1.png differ diff --git a/_images/webinar03_bgp2.png b/_images/webinar03_bgp2.png new file mode 100644 index 0000000..2ce2386 Binary files /dev/null and b/_images/webinar03_bgp2.png differ diff --git a/_images/webinar03_bgp3.png b/_images/webinar03_bgp3.png new file mode 100644 index 0000000..f03872d Binary files /dev/null and b/_images/webinar03_bgp3.png differ diff --git a/_images/xcvr_sn_list.png b/_images/xcvr_sn_list.png new file mode 100644 index 0000000..fc781b4 Binary files /dev/null and b/_images/xcvr_sn_list.png differ diff --git a/_images/xcvr_sn_list_filtered.png b/_images/xcvr_sn_list_filtered.png new file mode 100644 index 0000000..6448d60 Binary files /dev/null and b/_images/xcvr_sn_list_filtered.png differ diff --git a/_sources/changelog.rst.txt b/_sources/changelog.rst.txt new file mode 100644 index 0000000..eeda5df --- /dev/null +++ b/_sources/changelog.rst.txt @@ -0,0 +1,39 @@ +Language revisions / Change log +=============================== + +Revision per CloudVision release +-------------------------------- + +.. csv-table:: + :file: changelog_revision_per_release.csv + :header-rows: 1 + +Change log +---------- + +Revision 4 +^^^^^^^^^^ + +- Function :ref:`aggregate` +- Function :ref:`unnestTimeseries` +- Function :ref:`formatInt` +- Function :ref:`formatFloat` +- Function :ref:`strCut` +- Filter :ref:`applyDeletes` + +Revision 3 +^^^^^^^^^^ + +- Support for `named wildcards `_ replaces variable substitution +- Function :ref:`linregression` +- Function :ref:`ewlinregression` +- Filter :ref:`fields` +- Filter :ref:`setFields` +- Filter :ref:`renameFields` +- Filter :ref:`topK` +- Filter :ref:`bottomK` + +Revision 2 +^^^^^^^^^^ + +No change to the AQL syntax or standard library. \ No newline at end of file diff --git a/_sources/doc/filters.rst.txt b/_sources/doc/filters.rst.txt new file mode 100644 index 0000000..3d94c12 --- /dev/null +++ b/_sources/doc/filters.rst.txt @@ -0,0 +1,837 @@ +.. _field: + +field +^^^^^ + +:guilabel:`Added in revision 1` + +Filter ``field`` applies to a :ref:`timeseries` of :ref:`dicts `. Returns a :ref:`timeseries` of the value at the +specified key for each entry of the :ref:`timeseries` that contains this field. Entries that don't +contain the key are not in the output :ref:`timeseries`. + +* The filtered value is a :ref:`timeseries` of :ref:`dicts ` +* The first and only parameter is the key to keep in the :ref:`dicts ` + +.. code:: aqlp + + >>> `analytics:/path/to/data`[1] + timeseries{ + tstamp1: dict{key1: val1, key2: val2, key3: val3} + tstamp2: dict{key1: val4, key2: val5} + } + >>> _ | field("key1") + timeseries{ + tstamp1: val1 + tstamp2: val4 + } + +.. _fields: + +fields +^^^^^^ + +:guilabel:`Added in revision 3` + +Filter ``fields`` applies to a :ref:`dict`. It filters it and returns a new dict containing only the +key/value pairs whose key is passed as parameter. + +* The filtered value is a :ref:`dict` +* There is a variable (:math:`0` to :math:`n`) number of parameters: they are the keys to keep in the output dict + +.. note:: + + If a specified key is missing from the source :ref:`dict`, the filter will not fail but the + output :ref:`dict` will also be missing that key + +.. code:: aqlp + + >>> let d = `analytics:/path/to/data/*` | map(merge(_value)) + >>> d + dict{ + key0: dict{key1: val1, key2: val2, key3: val3} + key01: dict{key1: val4, key2: val5} + key02: dict{key1: val6, key2: val7} + } + >>> d | fields("key0", "key01") + dict{ + key0: dict{key1: val1, key2: val2, key3: val3} + key01: dict{key1: val4, key2: val5} + } + >>> d | fields("k") + dict{ + } + >>> d | fields() + dict{ + } + >>> d | fields("key01") | map(_value | fields("key2")) + dict{ + key01: dict{key2: val5} + } + +.. _setFields: + +setFields +^^^^^^^^^ + +:guilabel:`Added in revision 3` + +Filter ``setFields`` sets some key/value pairs in a :ref:`dict`. If the key already existed in the +filtered :ref:`dict`, its value will be replaced with the new one in the output :ref:`dict` (like all filters, +``setFields`` returns a filtered copy of the :ref:`dict` and does not alter the source). If the key did not +exist in the filtered :ref:`dict`, the key/value pair will just be added to the output :ref:`dict`. + +* The filtered value is a `dict` +* There is a variable (0 to n) even number of parameters: they correspond to the list of key/value + pairs + +.. code:: aqlp + + >>> let d = newDict() | setFields("k1", "v1", "k2", 2.3, "k3", 3) + >>> d + dict{ + k1: v1 + k2: 2.3 + k3: 3 + } + >>> d | setFields("k4", newDict() | setFields("k5", "v5")) + dict{ + k1: v1 + k2: 2.3 + k3: 3 + k4: dict{ + k5: v5 + } + } + >>> d + dict{ + k1: v1 + k2: 2.3 + k3: 3 + } # the source dict is not altered + +.. _applyDeletes: + +applyDeletes +^^^^^^^^^^^^ + +:guilabel:`Added in revision 4` + +Filter ``applyDeletes`` applies the deletes to a :ref:`timeseries`. This :ref:`timeseries` must be freshly returned by a query. +Most filters remove the deletes information from :ref:`timeseries`, so this should be called before any other filter or function. + +If no argument is passed, applying a delete will remove all entries with that delete's key that were updated prior to the delete +itself. This use-case is mostly appropriate when used with the result of a query that does not contain historical data (state-only). +With historical data, this would wipe deleted entries from ever having existed in the :ref:`timeseries`, instead of signaling the end +of the entry at the moment of deletion. + +If an argument is passed, then the expression defines a value that will be written at the moment of the delete for that key. +This use-case is more appropriate with historical data because it will not remove entries, but instead create an entry that signals +the end of the value. + +* The filtered value is a :ref:`timeseries` that still contains delete information. +* The only and optional parameter is the expression. Its value can be of any type after evaluation. + +Usable metavariables in the expression are: + + * ``_key`` or ``_1``: key matching the delete + * ``_index``: index of the delete in the :ref:`timeseries` + * ``_updindex``: index of the last update for this key in the :ref:`timeseries` + * ``_time`` or ``_2``: :ref:`time` of the delete in the :ref:`timeseries` + * ``_updtime``: :ref:`time` of the last update for this key in the :ref:`timeseries` + * ``_value`` or ``_3``: last value prior to the delete for the deleted key + * ``_src`` or ``_4``: reference to the :ref:`timeseries` being filtered + +.. TODO: make a shorter example to put outside of collapse block + +.. collapse:: Example + + .. code:: aqlp + + >>> let a = `analytics:/tags/BugAlerts/Query/gNMIEnabled`[5] + >>> a + timeseries{ + start: 2021-03-17 02:48:58.205235103 +0100 CET + end: 2022-10-19 13:30:33.722908 +0200 CEST + 2021-03-17 02:48:58.205235103 +0100 CET: dict{ + JAS12200014: true + JAS16040045: true + JAS17250006: true + JAS17250010: true + JAS17510146: true + JPE14171444: true + JPE17191574: true + SSJ17371234: true + } + 2021-05-12 17:32:58.269740014 +0200 CEST: dict{ + HSH14075043: true + HSH14075051: true + } + 2021-11-03 17:09:46.753872494 +0100 CET: dict{ + HSH14280171: true + HSH14420467: true + JPE14250224: true + JPE14383408: true + SSJ17049015: true + SSJ17374660: true + } + 2021-11-11 05:09:22.273451668 +0100 CET: dict{ + JAS14170008: true + JAS14210057: true + JAS17070003: true + JAS18170075: true + JPE14120478: true + JPE19280519: true + } + 2022-02-18 23:08:10.204460235 +0100 CET: dict{ + 2568DB4A33177968A78C4FD5A8232159: true + 6323DA7D2B542B5D09630F87351BEA41: true + BAD032986065E8DC14CBB6472EC314A6: true + CD0EADBEEA126915EA78E0FB4DC776CA: true + } + 2022-02-22 00:48:45.347884243 +0100 CET: dict{0123F2E4462997EB155B7C50EC148767: true} + 2022-07-18 18:10:07.772750473 +0200 CEST: dict{JPE20244151: true} + } + >>> deletes(a) + timeseries{ + start: 2021-03-17 02:48:58.205235103 +0100 CET + end: 2022-10-19 13:30:33.722908 +0200 CEST + 2021-11-23 11:09:21.716099165 +0100 CET: dict{ + HSH14075043: + HSH14075051: + HSH14280171: + HSH14420467: + JAS14170008: + JAS14210057: + JAS16040045: + JAS17070003: + JAS17250006: + JAS17250010: + JAS17510146: + JAS18170075: + JPE14120478: + JPE14171444: + JPE14250224: + JPE14383408: + JPE17191574: + JPE19280519: + SSJ17049015: + SSJ17371234: + SSJ17374660: + } + 2022-02-22 00:48:45.347884243 +0100 CET: dict{ + 2568DB4A33177968A78C4FD5A8232159: + 6323DA7D2B542B5D09630F87351BEA41: + BAD032986065E8DC14CBB6472EC314A6: + CD0EADBEEA126915EA78E0FB4DC776CA: + } + } + >>> a | applyDeletes() + timeseries{ + start: 2021-03-17 02:48:58.205235103 +0100 CET + end: 2022-10-19 13:30:33.722908 +0200 CEST + 2021-03-17 02:48:58.205235103 +0100 CET: dict{JAS12200014: true} + 2022-02-22 00:48:45.347884243 +0100 CET: dict{0123F2E4462997EB155B7C50EC148767: true} + 2022-07-18 18:10:07.772750473 +0200 CEST: dict{JPE20244151: true} + } + >>> a | applyDeletes(_key+" is deleted, its value was " + str(_value)) + timeseries{ + start: 2021-03-17 02:48:58.205235103 +0100 CET + end: 2022-10-19 13:30:33.722908 +0200 CEST + 2021-03-17 02:48:58.205235103 +0100 CET: dict{ + JAS12200014: true + JAS16040045: true + JAS17250006: true + JAS17250010: true + JAS17510146: true + JPE14171444: true + JPE17191574: true + SSJ17371234: true + } + 2021-05-12 17:32:58.269740014 +0200 CEST: dict{ + HSH14075043: true + HSH14075051: true + } + 2021-11-03 17:09:46.753872494 +0100 CET: dict{ + HSH14280171: true + HSH14420467: true + JPE14250224: true + JPE14383408: true + SSJ17049015: true + SSJ17374660: true + } + 2021-11-11 05:09:22.273451668 +0100 CET: dict{ + JAS14170008: true + JAS14210057: true + JAS17070003: true + JAS18170075: true + JPE14120478: true + JPE19280519: true + } + 2021-11-23 11:09:21.716099165 +0100 CET: dict{ + HSH14075043: HSH14075043 is deleted, its value was true + HSH14075051: HSH14075051 is deleted, its value was true + HSH14280171: HSH14280171 is deleted, its value was true + HSH14420467: HSH14420467 is deleted, its value was true + JAS14170008: JAS14170008 is deleted, its value was true + JAS14210057: JAS14210057 is deleted, its value was true + JAS16040045: JAS16040045 is deleted, its value was true + JAS17070003: JAS17070003 is deleted, its value was true + JAS17250006: JAS17250006 is deleted, its value was true + JAS17250010: JAS17250010 is deleted, its value was true + JAS17510146: JAS17510146 is deleted, its value was true + JAS18170075: JAS18170075 is deleted, its value was true + JPE14120478: JPE14120478 is deleted, its value was true + JPE14171444: JPE14171444 is deleted, its value was true + JPE14250224: JPE14250224 is deleted, its value was true + JPE14383408: JPE14383408 is deleted, its value was true + JPE17191574: JPE17191574 is deleted, its value was true + JPE19280519: JPE19280519 is deleted, its value was true + SSJ17049015: SSJ17049015 is deleted, its value was true + SSJ17371234: SSJ17371234 is deleted, its value was true + SSJ17374660: SSJ17374660 is deleted, its value was true + } + 2022-02-18 23:08:10.204460235 +0100 CET: dict{ + 2568DB4A33177968A78C4FD5A8232159: true + 6323DA7D2B542B5D09630F87351BEA41: true + BAD032986065E8DC14CBB6472EC314A6: true + CD0EADBEEA126915EA78E0FB4DC776CA: true + } + 2022-02-22 00:48:45.347884243 +0100 CET: dict{ + 2568DB4A33177968A78C4FD5A8232159: 2568DB4A33177968A78C4FD5A8232159 is deleted, its value was true + 6323DA7D2B542B5D09630F87351BEA41: 6323DA7D2B542B5D09630F87351BEA41 is deleted, its value was true + BAD032986065E8DC14CBB6472EC314A6: BAD032986065E8DC14CBB6472EC314A6 is deleted, its value was true + CD0EADBEEA126915EA78E0FB4DC776CA: CD0EADBEEA126915EA78E0FB4DC776CA is deleted, its value was true + } + 2022-02-22 00:48:45.347884243 +0100 CET: dict{0123F2E4462997EB155B7C50EC148767: true} + 2022-07-18 18:10:07.772750473 +0200 CEST: dict{JPE20244151: true} + } + +.. _renameFields: + +renameFields +^^^^^^^^^^^^ + +:guilabel:`Added in revision 3` + +Filter ``renameFields`` renames some keys in a :ref:`dict`. The keys which are not specified in the +arguments will be kept in the output dict. Use the :ref:`fields` filter to remove them. + +* The filtered value is a :ref:`dict` +* There is a variable (:math:`0` to :math:`n`) even number of parameters: they correspond to the list of old-key/new-key pairs + +.. note:: + + If a specified key is missing from the source dict, the filter will not fail and that pair + will just be ignored + +.. code:: aqlp + + >>> let d = `analytics:/path/to/data/*` | map(merge(_value)) + >>> d + dict{ + key0: dict{key1: val1, key2: val2, key3: val3} + key01: dict{key1: val4, key2: val5} + } + >>> d | renameFields("key0", "newkey0") + dict{ + newkey0: dict{key1: val1, key2: val2, key3: val3} + key01: dict{key1: val4, key2: val5} + } + >>> d | renameFields("key0", "newkey0", "key01", "newkey01") + dict{ + newkey0: dict{key1: val1, key2: val2, key3: val3} + newkey01: dict{key1: val4, key2: val5} + } + >>> d | fields("key01") | map(_value | renameFields("key2", "newkey2")) + dict{ + key01: dict{key1: val4, newkey2: val5} + } + +.. _where: + +where +^^^^^ + +:guilabel:`Added in revision 1` + +Filter ``where`` returns a filtered :ref:`timeseries` or :ref:`dict` containing exclusively the entries of the input where +the predicate passed as parameter is :ref:`true `. + +* The first and only parameter is the predicate. It is an expression, the value of which must be a boolean after evaluation. + +Usable metavariables in the predicate for :ref:`timeseries` are: + + * ``_index`` or ``_1``: index of the current element (starting at :math:`0`) + * ``_time`` or ``_2``: timestamp of the current element + * ``_value`` or ``_3``: value of the current element + * ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +Usable metavariables in the predicate for :ref:`dicts ` are: + + * ``_key`` or ``_1``: key of the current element + * ``_value`` or ``_2``: value of the current element + * ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("key1") + timeseries{ + tstamp1: 1 + tstamp2: 2 + tstamp3: 3 + tstamp4: 4 + } + >>> _ | where(_value >= 3) + timeseries{ + tstamp3: 3 + tstamp4: 4 + } + >>> let d = newDict() + >>> d["key1"] = 13 + >>> d["key2"] = 1 + >>> d["k3"] = 1 + >>> d["k4"] = 1 + >>> d | where(strContains(_key, "key")) + dict{ + "key1": 13 + "key2": 1 + } + +.. _map: + +map +^^^ + +:guilabel:`Added in revision 1` + +Filter ``map`` returns a :ref:`timeseries` or :ref:`dict` containing the results of the expression passed as parameter applied to each entry of the filtered :ref:`timeseries` or :ref:`dict`. + +* The first and only parameter is the expression. Its value can be of any type after evaluation. + +Usable metavariables in the expression for :ref:`timeseries` are: + +* ``_index`` or ``_1``: index of the current element (starting at :math:`0`) +* ``_time`` or ``_2``: timestamp of the current element +* ``_value`` or ``_3``: value of the current element +* ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +Usable metavariables in the expression for :ref:`dicts ` are: + +* ``_key`` or ``_1``: key of the current element +* ``_value`` or ``_2``: value of the current element +* ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] + timeseries{ + tstamp1: dict{key1: 1, key2: 12, key3: 11} + tstamp2: dict{key1: 2, key2: 123} + tstamp3: dict{key1: 3, key2: 78, key3: 42} + tstamp4: dict{key1: 4, key2: 68} + } + >>> _ | map(_value["key1"] + 1) + timeseries{ + tstamp1: 2 + tstamp2: 3 + tstamp3: 4 + tstamp4: 5 + } + >>> let d = newDict() + >>> d["key1"] = 13 + >>> d["key2"] = 1 + >>> d["k3"] = 1 + >>> d["k4"] = 1 + >>> d | map(_key + "l") + dict{ + "key1": key1l + "key2": key2l + "k3": k3l + "k4": k4l + } + >>> d | map(_value^2) + dict{ + "key1": 169 + "key2": 1 + "k3": 1 + "k4": 1 + } + +.. _mapne: + +mapne +^^^^^ + +:guilabel:`Added in revision 1` + +Filter ``mapne`` (map-not-empty) returns a :ref:`timeseries` or :ref:`dict` containing the results of the +expression passed as first parameter applied to the result of the expression passed as second parameter +if its result is not empty. This applies to each entry of the filtered :ref:`timeseries` or :ref:`dict`. + +* The first parameter is the main expression. Its value can be of any type after evaluation. + + Usable metavariables in the expression for :ref:`timeseries` are: + + * ``_index`` or ``_1``: index of the current element (starting at :math:`0`) + * ``_time`` or ``_2``: timestamp of the current element + * ``_value`` or ``_3``: result of the second expression applied to the current element + * ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + + Usable metavariables in the expression for :ref:`dict`s are: + + * ``_key`` or ``_1``: key of the current element + * ``_value`` or ``_2``: result of the second expression applied to the current element + * ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +* the second parameter is the filtering expression. Its value can be of any type after evaluation. + + Usable metavariables in the expression for :ref:`timeseries` are: + + * ``_index`` or ``_1``: index of the current element (starting at 0) + * ``_time`` or ``_2``: timestamp of the current element + * ``_value`` or ``_3``: value of the current element + * ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + + Usable metavariables in the expression for :ref:`dicts ` are: + + * ``_key`` or ``_1``: key of the current element + * ``_value`` or ``_3`` value of the current element + * ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +.. code:: aqlp + + >>> `analytics:/path/to/*/data/with/wildcard`[3] + dict { + pathElement1: timeseries{t1:1, t2:2, t3:3, t4:4} + pathElement2: timeseries{t5:5, t6:6, t7:7, t8:8} + pathElement3: timeseries{} + pathElement4: timeseries{t9:9, t10:10, t11:11, t12:12} + } + >>> _ | map(mean(_value)) + error: cannot compute mean of empty timeseries + >>> _ | mapne(mean(_value), _value) + dict { + pathElement1: 2.5 + pathElement2: 6.5 + pathElement4: 10.5 + } + >>> `analytics:/path/to/data`[3] + timeseries{ + tstamp1: dict{k1:1, k2:2, k3:3, k4:4} + tstamp2: dict{k1:1, k2:2, k3:3, k4:4} + tstamp3: dict{} + tstamp4: dict{k1:1, k2:2, k3:3, k4:4} + } + >>> _ | map(mean(_value)) + error: cannot compute mean of empty dict + >>> _ | mapne(mean(_value) + 12, _value) + timeseries{ + tstamp1: 14.5 + tstamp2: 14.5 + tstamp4: 14.5 + } + + +.. _recmap: + +recmap +^^^^^^ + +:guilabel:`Added in revision 1` + +Filter ``recmap`` returns a :ref:`timeseries` or :ref:`dict` containing the results of the expression passed as parameter applied to each +entry of the filtered :ref:`timeseries` or :ref:`dict`, at the specified depth. + +* The first parameter is the recursion depth (:ref:`num`). +* The second parameter is the expression. Its value can be of any type after evaluation. + +Usable metavariables in the expression for :ref:`timeseries` are: + +* ``_index`` or ``_1``: index of the current element (starting at :math:`0`) +* ``_time`` or ``_2``: timestamp of the current element +* ``_value`` or ``_3``: value of the current element +* ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +Usable metavariables in the expression for :ref:`dicts ` are: + +* ``_key`` or ``_1``: key of the current element +* ``_value`` or ``_2``: value of the current element +* ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +.. code:: aqlp + + >>> `analytics:/path/to/*/data/with/*/2/wildcards` + dict { + pe1: dict{pe1.1: timeseries{1, 2, 3}, pe1.2: timeseries{1, 2, 3}} + pe2: dict{pe2.1: timeseries{1, 2, 3}, pe2.2: timeseries{1, 2, 3}} + pe3: dict{pe3.1: timeseries{1, 2, 3}, pe3.2: timeseries{1, 2, 3}} + } # we want the same recursion depth for every branch here, and stop at the timeseries level + >>> let data = _ + >>> data | map(_value | map(mean(_value))) + dict { + pe1: dict{pe1.1: 2, pe1.2: 2} + pe2: dict{pe2.1: 2, pe2.2: 2} + pe3: dict{pe3.1: 2, pe3.2: 2} + } # nested map filters work but are very verbose + >>> data | recmap(2, mean(_value)) + dict { + pe1: dict{pe1.1: 2, pe1.2: 2} + pe2: dict{pe2.1: 2, pe2.2: 2} + pe3: dict{pe3.1: 2, pe3.2: 2} + } + # recmap is much clearer. + +.. _topK: + +topK +^^^^ + +:guilabel:`Added in revision 3` + +Filter ``topK`` filters the collection to keep only the k highest values. This filter can be +applied to a :ref:`timeseries` or a :ref:`dict`. + +* The first parameter is the ``k`` parameter, which is the number of values to keep in the filtered collection. +* The second parameter is an expression that returns for each entry of the collection the value to compare. + The return type of this expression must be comparable (:ref:`num`, :ref:`str`, :ref:`time`, or :ref:`duration`) + +Usable metavariables in the expression for :ref:`timeseries` are: + +* ``_index`` or ``_1``: index of the current element (starting at :math:`0`) +* ``_time`` or ``_2``: timestamp of the current element +* ``_value`` or ``_3``: value of the current element +* ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +Usable metavariables in the expression for :ref:`dicts ` are: + +* ``_key`` or ``_1``: key of the current element +* ``_value`` or ``_2``: value of the current element +* ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +.. code:: aqlp + + >>> let data = `analytics:/path/to/some/*/data` | map(merge(_value)) + >>> data + dict{ + Ethernet49/1: dict{ + in: 11.845057565692196 + out: 20.816078774499992 + } + Ethernet49/5: dict{ + in: 4.021321282808746 + out: 8.868898231943206 + } + Ethernet51/1: dict{ + in: 2.1800167411644353 + out: 2.413745251460854 + } + Ethernet51/2: dict{ + in: 3.126216167169341 + out: 26.05024018915018 + } + Ethernet51/3: dict{ + in: 54.1046901332212 + out: 5.035469519006775 + } + Ethernet51/4: dict{ + in: 7.313228804713885 + out: 4.899238295809337 + } + Ethernet8: dict{ + in: 0 + out: 71.6547381850231 + } + Management1: dict{ + in: 6.139184309225689 + out: 0.7010378175218949 + } + Port-Channel512: dict{ + in: 7.864572656164906 + out: 14.724350983923758 + } + Port-Channel532: dict{ + in: 16.652391153117858 + out: 9.562088032011452 + } + } + >>> data | topK(2, _value["in"]) + dict{ + Ethernet51/3: dict{ + in: 54.1046901332212 + out: 5.035469519006775 + } + Port-Channel532: dict{ + in: 16.652391153117858 + out: 9.562088032011452 + } + } + >>> data | map(_value["in"]) | topK(2, _value) + dict{ + Ethernet51/3: 54.1046901332212 + Port-Channel532: 16.652391153117858 + } + +.. _bottomK: + +bottomK +^^^^^^^ + +:guilabel:`Added in revision 3` + +Filter ``bottomK`` filters the collection to keep only the k lowest values. This filter can be +applied to a :ref:`timeseries` or a :ref:`dict`. + +* The first parameter is the ``k`` parameter, which is the number of values to keep in the filtered collection. +* The second parameter is an expression that returns for each entry of the collection the value to compare. + The return type of this expression must be comparable (:ref:`num`, :ref:`str`, :ref:`time`, or :ref:`duration`) + +Usable metavariables in the expression for :ref:`timeseries` are: + +* ``_index`` or ``_1``: index of the current element (starting at :math:`0`) +* ``_time`` or ``_2``: timestamp of the current element +* ``_value`` or ``_3``: value of the current element +* ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +Usable metavariables in the expression for :ref:`dicts ` are: + +* ``_key`` or ``_1``: key of the current element +* ``_value`` or ``_2``: value of the current element +* ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +.. code:: aqlp + + >>> let data = `analytics:/path/to/some/*/data` | map(merge(_value)) + >>> data + dict{ + Ethernet49/1: dict{ + in: 11.845057565692196 + out: 20.816078774499992 + } + Ethernet49/5: dict{ + in: 4.021321282808746 + out: 8.868898231943206 + } + Ethernet51/1: dict{ + in: 2.1800167411644353 + out: 2.413745251460854 + } + Ethernet51/2: dict{ + in: 3.126216167169341 + out: 26.05024018915018 + } + Ethernet51/3: dict{ + in: 54.1046901332212 + out: 5.035469519006775 + } + Ethernet51/4: dict{ + in: 7.313228804713885 + out: 4.899238295809337 + } + Ethernet8: dict{ + in: 0 + out: 71.6547381850231 + } + Management1: dict{ + in: 6.139184309225689 + out: 0.7010378175218949 + } + Port-Channel512: dict{ + in: 7.864572656164906 + out: 14.724350983923758 + } + Port-Channel532: dict{ + in: 16.652391153117858 + out: 9.562088032011452 + } + } + >>> data | bottomK(2, _value["in"]) + dict{ + Ethernet51/1: dict{ + in: 2.1800167411644353 + out: 2.413745251460854 + } + Ethernet8: dict{ + in: 0 + out: 71.6547381850231 + } + } + >>> data | map(_value["in"]) | bottomK(2, _value) + dict{ + Ethernet51/1: 2.1800167411644353 + Ethernet8: 0 + } + +.. _deepmap: + +deepmap +^^^^^^^ + +:guilabel:`Added in revision 1` + +Filter ``deepmap`` returns a :ref:`timeseries` or :ref:`dict` containing the results of the expression +passed as parameter applied to each entry of the filtered :ref:`timeseries` or :ref:`dict`, which can +contain nested :ref:`timeseries` or :ref:`dicts `. + +* The first and only parameter is the expression. Its value can be of any type after evaluation. +* Metavariables are applicable to the collection containing the leaf node to which the expression is applied, which can be nested under several layers. + +Usable metavariables in the expression for :ref:`timeseries` are: + +* ``_index`` or ``_1``: index of the current element (starting at :math:`0`) +* ``_time`` or ``_2``: timestamp of the current element +* ``_value`` or ``_3``: value of the current element +* ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +Usable metavariables in the expression for :ref:`dicts ` are: + +* ``_key`` or ``_1``: key of the current element +* ``_value`` or ``_2``: value of the current element +* ``_src`` or ``_4`` (revision 4+): reference to the :ref:`timeseries` or :ref:`dict` being filtered + +.. code:: aqlp + + >>> `analytics:/path/to/*/data/with/wildcard`[3] + dict { + pathElement1: timeseries{t1:1, t2:2, t3:3, t4:4} + pathElement2: timeseries{t5:5, t6:6, t7:7, t8:8} + pathElement3: timeseries{dict{k10:10}, dict{k11:11}} + } + >>> _ | deepmap(_value + 1) + dict { + pathElement1: timeseries{t1:2, t2:3, t3:4, t4:5} + pathElement2: timeseries{t5:6, t6:7, t7:8, t8:9} + pathElement3: timeseries{dict{k10:11}, dict{k11:12}} + } # recursion depth can be different between branches, deepmap will recurse as long as the value is either a dict or timeseries + +.. _resample: + +resample +^^^^^^^^ + +:guilabel:`Added in revision 1` + +Filter ``resample`` returns a :ref:`timeseries` resampled with the given :ref:`duration` as constant interval. +CloudVision :ref:`timeseries` are state-based, so any value in the output :ref:`timeseries` will be the latest +value prior to the output timestamp in the original :ref:`timeseries`. + +* The first and only parameter, of type :ref:`duration`, specifies the interval of the output :ref:`timeseries`. + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + 2019-08-31 00:00:00: 13 + 2019-08-31 00:06:23: 1 + 2019-08-31 00:08:29: 2 + 2019-08-31 00:11:43: 200 + } + >>> _ | resample(2m) + timeseries{ + 2019-08-31 00:00:00: 13 + 2019-08-31 00:02:00: 13 + 2019-08-31 00:04:00: 13 + 2019-08-31 00:06:00: 13 + 2019-08-31 00:08:00: 13 + 2019-08-31 00:10:00: 2 + 2019-08-31 00:12:00: 200 + } \ No newline at end of file diff --git a/_sources/doc/functions.rst.txt b/_sources/doc/functions.rst.txt new file mode 100644 index 0000000..bf8a28b --- /dev/null +++ b/_sources/doc/functions.rst.txt @@ -0,0 +1,1893 @@ +General functions +^^^^^^^^^^^^^^^^^^ + +.. _now: + +now +*** + +:guilabel:`Added in revision 1` + +Function ``now`` returns the current :ref:`time`. Time is constant across all the script, so that all operations and queries have the same reference time. + +.. code:: aqlp + + >>> now() + 2019-09-01T14:05:10Z + +.. _length: + +length +****** + +:guilabel:`Added in revision 1` + +Function ``length`` returns the number of elements in a :ref:`str`, a :ref:`timeseries` or a :ref:`dict`. + +* The first and only argument is a :ref:`str`, :ref:`timeseries` or :ref:`dict` + +.. code:: aqlp + + >>> length(`analytics:/path/to/data`[0]) + 1 + +.. _merge: + +merge +***** + +:guilabel:`Added in revision 1` + +Function ``merge`` returns a union of all the :ref:`dicts ` contained in a :ref:`timeseries`. In case of key collision, the latest entry is kept. + +* The first and only argument is a :ref:`timeseries` of :ref:`dicts ` + +.. code:: aqlp + + >>> `analytics:/path/to/data`[1] + timeseries{ + tstamp1: dict{key1: val1, key2: val2, key3: val3} + tstamp2: dict{key1: val4, key2: val5} + } + >>> merge(_) + dict{key1: val4, key2: val5, key: val3} + + +.. _deletes: + +deletes +******* + +:guilabel:`Added in revision 1` + +Function ``deletes`` returns a :ref:`timeseries` of :ref:`dicts ` used as sets of the delete keys from the input :ref:`timeseries`. +Only works with unfiltered :ref:`timeseries`, as most filters remove the deletes entries. An empty :ref:`dict` as value means the update is a DeleteAll + +* The first and only parameter is the `timeseries` + +.. code:: aqlp + + >>> deletes(`analytics:/path/to/data`[200]) + timeseries{ + tstamp1: dict{key1: nil, key2: nil} + tstamp2: dict{} # this deletes all keys + } + +.. _equal: + +equal +***** + +:guilabel:`Added in revision 1` + +Function ``equal`` performs a cross-type equality check on the given arguments using type coercions. + +* The first argument is a value of any type +* The second argument is a value of any type + +.. code:: aqlp + + >>> equal("1", 1) + true + >>> equal(1, true) + true + >>> equal(0, true) + false + +.. _complexKey: + +complexKey +********** + +:guilabel:`Added in revision 1` + +Function ``complexKey`` parses a string containing a literal or json object. +Numerical values without floating-point will produce an integer. +Numerical values with a floating point will produce a float64 (:ref:`num`). +Boolean literals will produce a boolean (:ref:`bool`). +Values surrounded with {} or [] will be parsed as JSON. +For all cases except float64 (:ref:`num`) and :ref:`bool`, the returned value will be of type :ref:`unknown` (internal interpreter type), but can be used to access complex keys in dicts. + +* The first and only parameter is the :ref:`str` to parse + +.. code:: aqlp + + >>> complexKey("1") + int(1) # AQL type is unknown + >>> complexKey("1.2") + float64(1.2) # AQL type is num + >>> complexKey("{\"key1\": 1, \"key2\": true}") + {"key1":1,"key2":true}# AQL type is unknown + >>> complexKey("[1, 2, true]") + [1,2,true] # AQL type is unknown + +Dicts functions +^^^^^^^^^^^^^^^ + +.. _newDict: + +newDict +******* + +:guilabel:`Added in revision 1` + +Function ``newDict`` returns a new empty :ref:`dict`. + +.. code:: aqlp + + >>> newDict() + dict{} + +.. _dictRemove: + +dictRemove +********** + +:guilabel:`Added in revision 1` + +Function ``dictRemove`` removes a given key from a :ref:`dict`. + +* The first argument is the :ref:`dict` +* The second argument is the key to remove + +.. code:: aqlp + + >>> let d = newDict() + >>> d["key"] = 1 + >>> d["key2"] = 2 + >>> d + dict{"key": 1, "key2": 2} + >>> dictRemove(d, "key") + >>> d + dict{"key2": 2} + +.. _dictHasKey: + +dictHasKey +********** + +:guilabel:`Added in revision 1` + +Function ``dictHasKey`` returns :ref:`true ` if a dict contains the specified key, :ref:`false ` if it doesn't. + +* The first parameter is the :ref:`dict` +* The second parameter is the key + +.. code:: aqlp + + >>> let d = newDict() + >>> d["key"] = 1 + >>> d["key2"] = 2 + >>> d + dict{"key": 1, "key2": 2} + >>> dictHasKey(d, "key") + true + >>> dictHasKey(d, "key3") + false + +.. _dictKeys: + +dictKeys +******** + +:guilabel:`Added in revision 1` + +Function ``dictKeys`` returns a :ref:`timeseries` with the list of keys in a :ref:`dict`. + +* The first and only parameter is the :ref:`dict` + +.. code:: aqlp + + >>> let d = newDict() + >>> d["key"] = 1 + >>> d["key2"] = 2 + >>> d + dict{"key": 1, "key2": 2} + >>> dictKeys(d) + timeseries{ + 0000-00-00 00:00:00.000000001: "key" + 0000-00-00 00:00:00.000000002: "key2" + } + +.. _unnestTimeseries: + +unnestTimeseries +**************** + +:guilabel:`Added in revision 4` + +Function ``unnestTimeseries`` merges multiple :ref:`timeseries` nested in :ref:`dicts `, pushes them to the top level, +and returns a single flattened :ref:`timeseries` where the former top-level :ref:`dicts ` are now nested within +the :ref:`timeseries`' values. The input data is typically what a query using ``*`` wildcards would return. + +* The first and only parameter is a :ref:`dict` that contains :ref:`timeseries`, either directly in the value, or nested in more levels of :ref:`dict`. + +.. TODO: make a smaller example to put outside the collapse + +.. collapse:: Example + + .. code:: aqlp + + >>> let ts = `analytics:/Devices/*/versioned-data/interfaces/data/*/aggregate/hardware/xcvr/1m`[1m] | recmap(2, _value | field("temperature") | field("avg")) + >>> let d = newDict() | setFields("AA", ts) + >>> d + dict{AA: dict{ + HSH14280171: dict{ + Ethernet50: timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:11:21.217673 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: 34.184087816704434 + 2022-08-16 17:11:00 +0200 CEST: 34.18519049983288 + } + Ethernet51: timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:11:21.217673 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: 34.84759166533158 + 2022-08-16 17:11:00 +0200 CEST: 34.84247911778802 + } + } + JAS17070003: dict{ + Ethernet50: timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:11:21.217673 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: 30.65325390983907 + 2022-08-16 17:11:00 +0200 CEST: 30.65664047328105 + } + Ethernet51: timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:11:21.217673 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: 27.58473123455124 + 2022-08-16 17:11:00 +0200 CEST: 27.597529745280074 + } + } + }} + >>> unnestTimeseries(d) + timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:11:21.217673 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: dict{AA: dict{ + HSH14280171: dict{ + Ethernet50: 34.184087816704434 + Ethernet51: 34.84759166533158 + } + JAS17070003: dict{ + Ethernet50: 30.65325390983907 + Ethernet51: 27.58473123455124 + } + }} + 2022-08-16 17:11:00 +0200 CEST: dict{AA: dict{ + HSH14280171: dict{ + Ethernet50: 34.18519049983288 + Ethernet51: 34.84247911778802 + } + JAS17070003: dict{ + Ethernet50: 30.65664047328105 + Ethernet51: 27.597529745280074 + } + }} + } + >>> let b = `analytics:/Devices/JPE17191574/versioned-data/interfaces/data/Ethernet50/aggregate/hardware/xcvr/1m`[2m] | field("voltage") | field("max") + >>> let dd = newDict() | setFields("BB", b) + >>> let ddd = newDict() | setFields("DD", ts, "EE", dd) + >>> d["FF"]=ddd + >>> d + dict{ + AA: dict{ + HSH14280171: dict{ + Ethernet50: timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:11:21.217673 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: 34.184087816704434 + 2022-08-16 17:11:00 +0200 CEST: 34.18519049983288 + } + Ethernet51: timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:11:21.217673 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: 34.84759166533158 + 2022-08-16 17:11:00 +0200 CEST: 34.84247911778802 + } + } + JAS17070003: dict{ + Ethernet50: timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:11:21.217673 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: 30.65325390983907 + 2022-08-16 17:11:00 +0200 CEST: 30.65664047328105 + } + Ethernet51: timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:11:21.217673 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: 27.58473123455124 + 2022-08-16 17:11:00 +0200 CEST: 27.597529745280074 + } + } + } + FF: dict{ + DD: dict{ + HSH14280171: dict{ + Ethernet50: timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:11:21.217673 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: 34.184087816704434 + 2022-08-16 17:11:00 +0200 CEST: 34.18519049983288 + } + Ethernet51: timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:11:21.217673 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: 34.84759166533158 + 2022-08-16 17:11:00 +0200 CEST: 34.84247911778802 + } + } + JAS17070003: dict{ + Ethernet50: timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:11:21.217673 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: 30.65325390983907 + 2022-08-16 17:11:00 +0200 CEST: 30.65664047328105 + } + Ethernet51: timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:11:21.217673 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: 27.58473123455124 + 2022-08-16 17:11:00 +0200 CEST: 27.597529745280074 + } + } + } + EE: dict{BB: timeseries{ + start: 2022-08-16 17:14:06.794969 +0200 CEST + end: 2022-08-16 17:16:06.794969 +0200 CEST + 2022-08-16 17:14:00 +0200 CEST: 3.2909 + 2022-08-16 17:15:00 +0200 CEST: 3.2909 + 2022-08-16 17:16:00 +0200 CEST: 3.2909 + }} + } + } + >>> unnestTimeseries(d) + timeseries{ + start: 2022-08-16 17:10:21.217673 +0200 CEST + end: 2022-08-16 17:16:06.794969 +0200 CEST + 2022-08-16 17:10:00 +0200 CEST: dict{ + AA: dict{ + HSH14280171: dict{ + Ethernet50: 34.184087816704434 + Ethernet51: 34.84759166533158 + } + JAS17070003: dict{ + Ethernet50: 30.65325390983907 + Ethernet51: 27.58473123455124 + } + } + FF: dict{DD: dict{ + HSH14280171: dict{ + Ethernet50: 34.184087816704434 + Ethernet51: 34.84759166533158 + } + JAS17070003: dict{ + Ethernet50: 30.65325390983907 + Ethernet51: 27.58473123455124 + } + }} + } + 2022-08-16 17:11:00 +0200 CEST: dict{ + AA: dict{ + HSH14280171: dict{ + Ethernet50: 34.18519049983288 + Ethernet51: 34.84247911778802 + } + JAS17070003: dict{ + Ethernet50: 30.65664047328105 + Ethernet51: 27.597529745280074 + } + } + FF: dict{DD: dict{ + HSH14280171: dict{ + Ethernet50: 34.18519049983288 + Ethernet51: 34.84247911778802 + } + JAS17070003: dict{ + Ethernet50: 30.65664047328105 + Ethernet51: 27.597529745280074 + } + }} + } + 2022-08-16 17:14:00 +0200 CEST: dict{FF: dict{EE: dict{BB: 3.2909}}} + 2022-08-16 17:15:00 +0200 CEST: dict{FF: dict{EE: dict{BB: 3.2909}}} + 2022-08-16 17:16:00 +0200 CEST: dict{FF: dict{EE: dict{BB: 3.2909}}} + } + + +Data Analysis Functions +^^^^^^^^^^^^^^^^^^^^^^^ + +.. _groupby: + +groupby +******* + +:guilabel:`Added in revision 1` + +Function ``groupby``, applied to a :ref:`timeseries`, returns a :ref:`dict` with keys corresponding to the 'group by field' parameter, +and values corresponding to the associate method and field. + +The function takes 4 parameters: + +* A :ref:`timeseries` of :ref:`dicts ` to apply this function to +* The name of the 'group by field' (a :ref:`str`) +* The name of one of the supported associative methods (a :ref:`str`) +* The name of the field whose values will be operated on by the associative method (a :ref:`str`) + +The entries in the :ref:`timeseries` are grouped by the values of the field from parameter 1. +For each entry, the value corresponding to the associative field of parameter 4 is obtained. +This results in a map with entries of the following format: + +* *key*: values of the 'group by field' +* *value*: lists of values of the 'associative field' + +On each of these lists, the following associative methods can be applied: + +* ``count``: returns the length of the list (i.e. the item count) +* ``max``: returns the max entry in the list +* ``mean``: returns the mean of the values in the list +* ``min``: returns the min entry in the list +* ``sum``: returns the sum of the entries in the list + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] + timeseries{ + tstamp1: dict{"name": "name1", "value": 1} + tstamp2: dict{"name": "name2", "value": 10} + tstamp3: dict{"name": "name1", "value": 2} + tstamp4: dict{"name": "name2", "value": 11} + } + >>> let ts = _ + >>> groupby(ts, "name", "mean", "value") + dict{ + "name1": 1.5 + "name2": 10.5 + } + >>> groupby(ts, "name", "count", "value") + dict{ + "name1": 2 + "name2": 2 + } + >>> groupby(ts, "name", "sum", "value") + dict{ + "name1": 3 + "name2": 21 + } + +.. _histogram: + +histogram +********* + +:guilabel:`Added in revision 1` + +Function ``histogram``, for a given :ref:`timeseries` of non-dict values, returns a :ref:`dict` with entries of the following format: + +* *key*: value in the :ref:`timeseries` (range if a :ref:`timeseries` of :ref:`num` values) +* *value*: time-weighted frequency in the timeseries + +Arguments: + +* A :ref:`timeseries` of non-dict values is the only argument to this function + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("strfield") + timeseries{ + start: 2019-08-31 00:00:00 + end: 2019-08-31 00:12:00 + 2019-08-31 00:00:00: "string1" + 2019-08-31 00:01:00: "string2" + 2019-08-31 00:10:00: "string1" + 2019-08-31 00:11:00: "string1" + } + >>> histogram(_) + dict{ + "string1": 0.25 + "string2": 0.75 + } # the count is weighted accordingly to the intervals + >>> `analytics:/path/to/data`[5] | field("numfield") + timeseries{ + start: 2019-08-31 00:00:00 + end: 2019-08-31 01:00:00 + 2019-08-31 00:00:00: 1 + 2019-08-31 00:01:00: 1.01 + 2019-08-31 00:10:00: 1.011 + 2019-08-31 00:30:00: 5.2 + 2019-08-31 00:44:00: 5.22 + 2019-08-31 00:56:00: 5.23 + } + >>> histogram(_) + dict{ + "1.0-1.011": 0.5 + "5.2-5.23": 0.5 + } # the count is weighted accordingly to the intervals + +.. _dhistogram: + +dhistogram +********** + +:guilabel:`Added in revision 1` + +Function ``dhistogram``, has a similar behaviour as :ref:`histogram` but its result is not time-weighted. +For a given :ref:`timeseries` of non-`dict` values, returns a :ref:`dict` with entries of the following format: + +* *key*: value in the :ref:`timeseries` (range if a :ref:`timeseries` of :ref:`num` values) +* *value*: frequency (non-weighted count of occurences) in the :ref:`timeseries` + +Arguments: + +A :ref:`timeseries` of non-dict values is the only argument to this function + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("strfield") + timeseries{ + start: 2019-08-31 00:00:00 + end: 2019-08-31 00:05:00 + 2019-08-31 00:00:00: "string1" + 2019-08-31 00:01:00: "string2" + 2019-08-31 00:10:00: "string1" + 2019-08-31 00:11:00: "string1" + } # the count does not depend of the time intervals between the updates + >>> dhistogram(_) + dict{ + "string1": 3 + "string2": 1 + } # the count does not depend of the time intervals between the updates + >>> `analytics:/path/to/data`[5] | field("numfield") + timeseries{ + 2019-08-31 00:00:00: 1 + 2019-08-31 00:01:00: 1.01 + 2019-08-31 00:10:00: 1.011 + 2019-08-31 00:30:00: 5.2 + 2019-08-31 00:44:12: 5.22 + 2019-08-31 02:01:34: 5.23 + } + >>> dhistogram(_) + dict{ + "1.0-1.011": 3 + "5.2-5.23": 3 + } # the count does not depend of the time intervals between the updates + +.. _aggregate: + +aggregate +********* + +:guilabel:`Added in revision 4` + +Function ``aggregate`` merges multiple :ref:`timeseries` contained in a :ref:`dict` (like the result of a +wildcarded query) using the associative method specified in the second parameter. The :ref:`dict` must +contain :ref:`timeseries`, all of which must contain identical timestamps. + +If one of the :ref:`timeseries` is empty, it will be ignored. + +If some values' timestamps are not matched in all the other non-empty :ref:`timeseries` of the :ref:`dict`, +these timestamp-value pairs will not be present in the output timeseries. + +``aggregate`` returns a simple :ref:`timeseries` with the aggregated data of all the input :ref:`timeseries`. + +* The first argument is the :ref:`dict` containing :ref:`timeseries` to aggregate +* The second argument is the name of the associative method to apply + +Like with :ref:`groupby`, the following associative methods can be applied: + +* ``count``: returns the length of the list (i.e. the item count) +* ``max``: returns the max entry in the list (requires the :ref:`timeseries` to be numerical) +* ``mean``: returns the mean of the values in the list (requires the :ref:`timeseries` to be numerical) +* ``min``: returns the min entry in the list (requires the :ref:`timeseries` to be numerical) +* ``sum``: returns the sum of the entries in the list (requires the :ref:`timeseries` to be numerical) + +.. code:: aqlp + + >>> let data = `analytics:/Devices/*/versioned-data/interfaces/data/*/aggregate/hardware/xcvr/15m`[1h] + >>> let avg = data | recmap(2, _value | field("temperature") | field("avg")) + >>> avg + JPE123456: dict{ + Ethernet1: timeseries{ + start: 2021-11-09 13:02:18.923904 +0000 GMT + end: 2021-11-09 13:32:18.923904 +0000 GMT + 2021-11-09 13:00:00 +0000 GMT: 28.64315689104305 + 2021-11-09 13:15:00 +0000 GMT: 28.64771549594622 + 2021-11-09 13:30:00 +0000 GMT: 28.647003241959368 + } + Ethernet2: timeseries{ + start: 2021-11-09 13:02:18.923904 +0000 GMT + end: 2021-11-09 13:32:18.923904 +0000 GMT + 2021-11-09 13:00:00 +0000 GMT: 26.52073192182222 + 2021-11-09 13:15:00 +0000 GMT: 26.57132998707 + 2021-11-09 13:30:00 +0000 GMT: 26.562415784963335 + } + [...] + } + JPE654321: dict{ + Ethernet1: timeseries{ + start: 2021-11-09 13:02:18.923904 +0000 GMT + end: 2021-11-09 13:32:18.923904 +0000 GMT + 2021-11-09 13:00:00 +0000 GMT: 27.872056741171672 + 2021-11-09 13:15:00 +0000 GMT: 26.422506200403397 + 2021-11-09 13:30:00 +0000 GMT: 27.889330661612725 + } + Ethernet2: timeseries{ + start: 2021-11-09 13:02:18.923904 +0000 GMT + end: 2021-11-09 13:32:18.923904 +0000 GMT + 2021-11-09 13:00:00 +0000 GMT: 25.501376131906685 + 2021-11-09 13:15:00 +0000 GMT: 24.06172043150084 + 2021-11-09 13:30:00 +0000 GMT: 25.520567819910998 + } + [...] + } + [...] + >>> let deviceAvg = avg | map(aggregate(_value, "mean")) + >>> deviceAvg + JPE123456: timeseries{ + start: 2021-11-09 13:02:18.923904 +0000 GMT + end: 2021-11-09 13:32:18.923904 +0000 GMT + 2021-11-09 13:00:00 +0000 GMT: 29.46781924765547 + 2021-11-09 13:15:00 +0000 GMT: 28.739134103556832 + 2021-11-09 13:30:00 +0000 GMT: 29.756429823529587 + } + JPE654321: timeseries{ + start: 2021-11-09 13:02:18.923904 +0000 GMT + end: 2021-11-09 13:32:18.923904 +0000 GMT + 2021-11-09 13:00:00 +0000 GMT: 27.581944406432633 + 2021-11-09 13:15:00 +0000 GMT: 27.60952274150811 + 2021-11-09 13:30:00 +0000 GMT: 27.60470951346135 + } + [...] + >>> aggregate(deviceAvg, "mean") # average temp accross all interfaces of all devices + timeseries{ + start: 2021-11-09 13:02:18.923904 +0000 GMT + end: 2021-11-09 13:32:18.923904 +0000 GMT + 2021-11-09 13:00:00 +0000 GMT: 33.261383897237806 + 2021-11-09 13:15:00 +0000 GMT: 33.16722874112898 + 2021-11-09 13:30:00 +0000 GMT: 33.37487749955894 + } + +Math functions +^^^^^^^^^^^^^^ + +.. _abs: + +abs +*** + +:guilabel:`Added in revision 1` + +Function ``abs`` returns the absolute value (:ref:`num`) of the given value. + +* The first and only argument :math:`x` is the value (:ref:`num`) of which the absolute value :math:`\lvert x \lvert` is wanted + +.. code:: aqlp + + >>> abs(-11) + 11 + >>> abs(200) + 200 + +.. _ceil: + +ceil +**** + +:guilabel:`Added in revision 1` + +Function ``ceil`` returns the closest integer (:ref:`num`) succeeding the given value. + +* The first and only argument :math:`x` is the value (:ref:`num`) of which the ceil :math:`\lceil x \rceil` is wanted + +.. code:: aqlp + + >>> ceil(12) + 12 + >>> ceil(12.1) + 13 + >>> ceil(-12.1) + -12 + +.. _floor: + +floor +***** + +:guilabel:`Added in revision 1` + +Function ``floor`` returns the closest integer (:ref:`num`) preceding the given value. + +* The first and only argument :math:`x` is the value (:ref:`num`) of which the floor :math:`\lfloor x \rfloor` is wanted + +.. code:: aqlp + + >>> floor(3) + 3 + >>> floor(3.2) + 3 + >>> floor(-3.2) + -4 + +.. _trunc: + +trunc +***** + +:guilabel:`Added in revision 1` + +Function ``trunc`` returns the truncated (:ref:`num`) given value. + +* The first and only argument is the value (:ref:`num`) to be truncated + +.. code:: aqlp + + >>> trunc(2.6) + 2 + >>> trunc(-2.49) + -2 + +.. _exp: + +exp +*** + +:guilabel:`Added in revision 1` + +Function ``exp`` returns the exponential (:ref:`num`) of the given value. + +* The first and only argument :math:`x` is the value (:ref:`num`) of which the exp :math:`e^x` is wanted + +.. code:: aqlp + + >>> exp(0) + 1 + >>> exp(12.1) + 179871.86225375105 + +.. _factorial: + +factorial +********* + +:guilabel:`Added in revision 1` + +Function ``factorial`` returns the factorial (:ref:`num`) of the given value. + +* The first and only argument :math:`x` is the value (:ref:`num`) of which the factorial :math:`x!` is wanted + +.. code:: aqlp + + >>> factorial(3) + 6 + +.. _gcd: + +gcd +*** + +:guilabel:`Added in revision 1` + +Function ``gcd`` returns the greatest common divisor (:ref:`num`) of two given integers. + +* The first two arguments are the integers (:ref:`num`) of which the GCD is wanted + +.. code:: aqlp + + >>> gcd(25, 30) + 5 + +.. _log: + +log +*** + +:guilabel:`Added in revision 1` + +Function ``log`` returns the natural log (:ref:`num`) of the given value. + +* The first and only argument :math:`x` is the value (:ref:`num`) of which the natural log :math:`log_e x` is wanted + +.. code:: aqlp + + >>> log(10) + 2.302585092994046 + +.. _log10: + +log10 +***** + +:guilabel:`Added in revision 1` + +Function ``log10`` returns the decimal log (:ref:`num`) of the given value. + +* The first and only argument :math:`x` is the value (:ref:`num`) of which the decimal log :math:`log_{10} x` is wanted + +.. code:: aqlp + + >>> log10(10) + 1 + +.. _pow: + +pow +*** + +:guilabel:`Added in revision 1` + +Function ``pow`` returns the first given value (:ref:`num`) to the power of the second given value. + +* The two arguments are the values :math:`x` (:ref:`num`), :math:`y` (:ref:`num`) used to compute :math:`x^y` + +.. code:: aqlp + + >>> pow(3, 2) + 9 + >>> pow(9, 1/2) + 3 + +.. _round: + +round +***** + +:guilabel:`Added in revision 1` + +Function ``round`` returns the rounded (:ref:`num`) given value. + +* The first and only argument :math:`x` is the value used to compute :math:`\lfloor x\rceil` i.e. the rounded value (:ref:`num`) + +.. code:: aqlp + + >>> round(2.5) + 3 + >>> round(2.49) + 2 + +.. _sqrt: + +sqrt +**** + +:guilabel:`Added in revision 1` + +Function ``sqrt`` returns the square root (:ref:`num`) of the given value. + +* The first and only argument :math:`x` is the value (:ref:`num`) of which the square root :math:`\sqrt{x}` is wanted + +.. code:: aqlp + + >>> sqrt(9) + 3 + +.. _max: + +max +*** + +:guilabel:`Added in revision 1` + +Function ``max`` returns the max value (:ref:`num`) in a :ref:`timeseries` or a :ref:`dict`. + +* The first and only argument is a :ref:`timeseries` or :ref:`dict` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> max(_) + 200 + +.. _min: + +min +*** + +:guilabel:`Added in revision 1` + +Function ``min`` returns the min value (:ref:`num`) in a :ref:`timeseries` or a :ref:`dict`. + +* The first and only argument is a :ref:`timeseries` or :ref:`dict` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> min(_) + 1 + +.. _formatInt: + +formatInt +********* + +:guilabel:`Added in revision 4` + +Function ``formatInt`` formats a num into a str using the specified base. The num will be treated +as an integer and any decimal part will be truncated. + +* The first argument is the :ref:`num` to convert +* The second argument is the base (:ref:`num`) + +.. code:: aqlp + + >>> formatInt(4, 2) + 100 + >>> formatInt(4.5, 2) + 100 + >>> formatInt(33, 2) + 100001 + >>> formatInt(15, 16) + f + >>> formatInt(29, 16) + 1d + >>> type(_) + str + +.. _formatFloat: + +formatFloat +*********** + +:guilabel:`Added in revision 4` + +Function ``formatFloat`` formats a :ref:`num` (``float64``) into a :ref:`str`, according to the specified format and +precision. + +* The first argument is the :ref:`num` to convert +* The second argument is a :ref:`str` of one letter describing the format: + + * ``'b'``: binary exponent + * ``'e'``: decimal exponent + * ``'f'``: no exponent + * ``'x'``: hexadecimal fraction and binary exponent + +* The third argument is :ref:a `num` specifying the precision, i.e. the number of digits after the decimal + point + +.. code:: aqlp + + >>> formatFloat(15682.8729, "e", 10) + 1.5682872900e+04 + >>> formatFloat(15682.8729, "f", 10) + 15682.8729000000 + >>> formatFloat(15, "x", 3) + 0x1.e00p+03 + >>> formatFloat(15, "b", 3) + 8444249301319680p-49 + >>> type(_) + str + +Stats functions +^^^^^^^^^^^^^^^ + +.. _dsum: + +dsum +**** + +:guilabel:`Added in revision 1` + +Function ``dsum`` returns the non-weighted sum of values (:ref:`num`) in a :ref:`timeseries`. + +* The first and only argument is a :ref:`timeseries` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> dsum(_) + 216 + +.. _dmean: + +dmean +***** + +:guilabel:`Added in revision 1` + +Function ``dmean`` returns the non-weighted mean value (:ref:`num`) of a :ref:`timeseries`. + +* The first and only argument is a :ref:`timeseries` containing plain :ref:`num` values + + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> dmean(_) + 54 + +.. _dmedian: + +dmedian +******* + +:guilabel:`Added in revision 1` + +Function ``dmedian`` returns the non-weighted median (:ref:`num`) of a :ref:`timeseries`. + +* The first and only argument is a :ref:`timeseries` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> dmedian(_) + 2 + +.. _dpercentile: + +dpercentile +*********** + +:guilabel:`Added in revision 1` + +Function ``dpercentile`` returns the non-weighted nth percentile (:ref:`num`) of a :ref:`timeseries`. + +* The first argument is a :ref:`timeseries` containing plain :ref:`num` values +* The second argument is a :ref:`num` specifying the percentile. If it is greater than 100 or lower than 0, the return value will be 0. + + +.. code:: aqlp + + >>> let a = `analytics:/path/to/data`[3] | field("numfield") + >>> a + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> dpercentile(a, 50) + 2 + >>> dpercentile(a, 90) + 200 + +.. _dvariance: + +dvariance +********* + +:guilabel:`Added in revision 1` + +Function ``dvariance`` returns the non-weighted statistical variance (:ref:`num`) of a :ref:`timeseries`. + +* The first and only argument is a :ref:`timeseries` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> dvariance(_) + 9503.333333333334 + +.. _dstddev: + +dstddev +******* + +:guilabel:`Added in revision 1` + +Function ``dstddev`` returns the non-weighted standard deviation (:ref:`num`) of a :ref:`timeseries`. + +* The first and only argument is a :ref:`timeseries` containing plain :ref:`num` values + +.. code:: aqlp + + >>> let a = `analytics:/path/to/data`[3] | field("numfield") + >>> a + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> dstddev(a) + 97.48504158758581 + >>> sqrt(dvariance(a)) + 97.48504158758581 + +.. _dskew: + +dskew +***** + +:guilabel:`Added in revision 1` + +Function ``dskew`` returns the non-weighted skewness of distribution (:ref:`num`) for data in a :ref:`timeseries`. + +* The first and only argument is a :ref:`timeseries` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> dskew(_) + 0.7431002727844832 + +dkurtosis +********* + +:guilabel:`Added in revision 1` + +Function ``dkurtosis`` returns the non-weighted kurtosis of distribution (:ref:`num`) for data in a :ref:`timeseries`. + +* The first and only argument is a :ref:`timeseries` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> dkurtosis(_) + -1.6923313578244437 + +.. _sum: + +sum +*** + +:guilabel:`Added in revision 1` + +Function ``sum`` returns the sum of the :ref:`num` values in a :ref:`timeseries` or a :ref:`dict`. +If applied to a :ref:`timeseries`, the result is time-weighted. + +* The first and only argument is a :ref:`dict` or :ref:`timeseries` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> sum(_) + 216 + >>> let d = newDict() + >>> d["key1"] = 13 + >>> d["key2"] = 1 + >>> d["key3"] = 2 + >>> d["key4"] = 200 + >>> sum(d) + 216 + +.. _mean: + +mean +**** + +:guilabel:`Added in revision 1` + +Function ``mean`` returns the mean of the :ref:`num` values in a :ref:`timeseries` or a :ref:`dict`. If applied to a :ref:`timeseries`, the result is time-weighted. + +* The first and only argument is a :ref:`dict` or :ref:`timeseries` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> mean(_) + 54 # will be different from dmean if space between the timestamps (weight) is not constant + >>> let d = newDict() + >>> d["key1"] = 13 + >>> d["key2"] = 1 + >>> d["key3"] = 2 + >>> d["key4"] = 200 + >>> mean(d) + 54 + +.. _median: + +median +****** + +:guilabel:`Added in revision 1` + +Function ``median`` returns the median of the :ref:`num` values in a :ref:`timeseries` or a :ref:`dict`. If applied to a :ref:`timeseries`, the result is time-weighted. + +* The first and only argument is a :ref:`dict` or :ref:`timeseries` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> median(_) + 2 # will be different from dmedian if space between the timestamps (weight) is not constant + >>> let d = newDict() + >>> d["key1"] = 13 + >>> d["key2"] = 1 + >>> d["key3"] = 2 + >>> d["key4"] = 200 + >>> median(d) + 2 + +.. _percentile: + +percentile +********** + +:guilabel:`Added in revision 1` + +Function ``percentile`` returns the time-weighted nth percentile (:ref:`num`) of a :ref:`timeseries` or a :ref:`dict`. +If applied to a :ref:`timeseries`, the result is time-weighted. + +* The first argument is a :ref:`timeseries` or a :ref:`dict` containing plain :ref:`num` values +* The second argument is a :ref:`num` specifying the percentile. If it is greater than :math:`100` or lower than :math:`0`, the return value will be :math:`0`. + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> percentile(_, 90) + 200 # will be different from dpercentile if space between the timestamps (weight) is not constant + >>> let d = newDict() + >>> d["key1"] = 13 + >>> d["key2"] = 1 + >>> d["key3"] = 2 + >>> d["key4"] = 200 + >>> percentile(d, 90) + 200 + +.. _variance: + +variance +******** + +:guilabel:`Added in revision 1` + +Function ``variance`` returns the statistical variance of the :ref:`num` values in a :ref:`timeseries` or a :ref:`dict`. +If applied to a :ref:`timeseries`, the result is time-weighted. + +* The first and only argument is a :ref:`dict` or :ref:`timeseries` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> variance(_) + 9503.333333333334 # will be different from dvariance if space between the timestamps (weight) is not constant + >>> let d = newDict() + >>> d["key1"] = 13 + >>> d["key2"] = 1 + >>> d["key3"] = 2 + >>> d["key4"] = 200 + >>> variance(d, 90) + 9503.33333333 + +.. _stddev: + +stddev +****** + +:guilabel:`Added in revision 1` + +Function ``stddev`` returns the standard deviation of the :ref:`num` values in a :ref:`timeseries` or a :ref:`dict`. +If applied to a :ref:`timeseries`, the result is time-weighted. + +* The first and only argument is a :ref:`dict` or :ref:`timeseries` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> stddev(_) + 97.485041588 # will be different from dstddev if space between the timestamps (weight) is not constant + >>> let d = newDict() + >>> d["key1"] = 13 + >>> d["key2"] = 1 + >>> d["key3"] = 2 + >>> d["key4"] = 200 + >>> stddev(d, 90) + 97.485041588 + +.. _skew: + +skew +**** + +:guilabel:`Added in revision 1` + +Function ``skew`` returns the skewness of dist.n of the :ref:`num` values in a :ref:`timeseries` or a :ref:`dict`. +If applied to a :ref:`timeseries`, the result is time-weighted. If the :ref:`timeseries` has exactly one element, :math:`0` is returned. + +* The first and only argument is a :ref:`dict` or :ref:`timeseries` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> skew(_) + 0.7431002727844832 # will be different from dskew if space between the timestamps (weight) is not constant + >>> let d = newDict() + >>> d["key1"] = 13 + >>> d["key2"] = 1 + >>> d["key3"] = 2 + >>> d["key4"] = 200 + >>> skew(d, 90) + 0.7431002727844832 + +.. _kurtosis: + +kurtosis +******** + +:guilabel:`Added in revision 1` + +Function ``kurtosis`` returns the kurtosis of dist.n of the :ref:`num` values in a :ref:`timeseries` or a :ref:`dict`. +If applied to a :ref:`timeseries`, the result is time-weighted. If the :ref:`timeseries` has exactly one element, :math:`0` is returned. + +* The first and only argument is a :ref:`dict` or :ref:`timeseries` containing plain :ref:`num` values + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + tstamp1: 13 + tstamp2: 1 + tstamp3: 2 + tstamp4: 200 + } + >>> skew(_) + 0.7431002727844832 # will be different from dskew if space between the timestamps (weight) is not constant + >>> let d = newDict() + >>> d["key1"] = 13 + >>> d["key2"] = 1 + >>> d["key3"] = 2 + >>> d["key4"] = 200 + >>> skew(d, 90) + 0.7431002727844832 + +.. _rate: + +rate +**** + +:guilabel:`Added in revision 1` + +Function ``rate`` returns a :ref:`timeseries` of rates computed from the initial :ref:`timeseries`' :ref:`num` values. + +* The first and only argument is the input :ref:`timeseries` of :ref:`num` + +.. code:: aqlp + + >>> `analytics:/path/to/data`[3] | field("numfield") + timeseries{ + 2019-08-31 00:00:00: 1 + 2019-08-31 00:01:00: 10 + 2019-08-31 00:02:00: 50 + 2019-08-31 00:03:00: 110 + 2019-08-31 00:04:00: 230 + } + >>> rate(_) + timeseries{ + 2019-08-31 00:00:00: 0.016666666666666666 + 2019-08-31 00:01:00: 0.15 + 2019-08-31 00:02:00: 0.6666666666666666 + 2019-08-31 00:03:00: 1 + 2019-08-31 00:04:00: 2 + } + +.. _linregression: + +linregression +************* + +:guilabel:`Added in revision 3` + +Function ``linregression`` produces a linear fit of a :ref:`timeseries` of :ref:`num`. + +* The first and only argument is the input :ref:`timeseries` of :ref:`num` + +It returns a :ref:`dict` with 4 entries ``slope``, ``intercept``, ``R2`` and ``fit``. The ``fit`` entry is a :ref:`timeseries` +with :ref:`num` values corresponding to the fitted line on the input :ref:`timeseries`' timestamps. The ``slope`` +and ``intercept`` are in seconds. + +.. code:: aqlp + + >>> `analytics:/path/to/data`[5m] | field("numfield") + timeseries{ + start: 2021-10-14 13:57:55.000545 +0100 IST + end: 2021-10-14 14:02:55.000545 +0100 IST + 2021-10-14 13:57:00 +0100 IST: 3.801633107494212e-05 + 2021-10-14 13:58:00 +0100 IST: 7.653320559746086e-05 + 2021-10-14 13:59:00 +0100 IST: 3.971200542852744e-05 + 2021-10-14 14:00:00 +0100 IST: 3.981215360110563e-05 + 2021-10-14 14:01:00 +0100 IST: 5.2121957107961934e-05 + 2021-10-14 14:02:00 +0100 IST: 3.838672949594982e-05 + } + >>> linregression(_) + dict{ + R2: 0.06273653863866613 + fit: timeseries{ + start: 2021-10-14 13:57:00 +0100 IST + end: 2021-10-14 14:02:00 +0100 IST + 2021-10-14 13:57:00 +0100 IST: 5.252194027605128e-05 + 2021-10-14 13:58:00 +0100 IST: 5.0485322987015024e-05 + 2021-10-14 13:59:00 +0100 IST: 4.844870569797877e-05 + 2021-10-14 14:00:00 +0100 IST: 4.641208840183708e-05 + 2021-10-14 14:01:00 +0100 IST: 4.4375471112800824e-05 + 2021-10-14 14:02:00 +0100 IST: 4.2338853823764566e-05 + } + intercept: 55.47126937459381 + slope: -3.39436215194667e-08 + } + +.. _ewlinregression: + +ewlinregression +*************** + +:guilabel:`Added in revision 3` + +Function ``ewlinregression`` produces a linear fit of a :ref:`timeseries` of :ref:`num` using exponentially +decaying weights. The older the value in the :ref:`timeseries` the smaller the weight. The latest value is +always given a weight of :math:`1`. + +* The first argument is the input :ref:`timeseries` of :ref:`num` +* The second argument is the desired weight that a point with time x seconds in the past would have +* the third argument is how long ago that time :math:`x` is, in seconds + + +It returns a :ref:`dict` with 4 entries ``slope``, ``intercept``, ``R2`` and ``fit``. The ``fit`` entry is a :ref:`timeseries` +with :ref:`num` values corresponding to the fitted line on the input :ref:`timeseries`' timestamps. The ``slope`` +and ``intercept`` are in seconds. + +.. code:: aqlp + + >>> `analytics:/path/to/data`[5m] | field("numfield") + timeseries{ + start: 2021-10-14 13:57:55.000545 +0100 IST + end: 2021-10-14 14:02:55.000545 +0100 IST + 2021-10-14 13:57:00 +0100 IST: 3.801633107494212e-05 + 2021-10-14 13:58:00 +0100 IST: 7.653320559746086e-05 + 2021-10-14 13:59:00 +0100 IST: 3.971200542852744e-05 + 2021-10-14 14:00:00 +0100 IST: 3.981215360110563e-05 + 2021-10-14 14:01:00 +0100 IST: 5.2121957107961934e-05 + 2021-10-14 14:02:00 +0100 IST: 3.838672949594982e-05 + } + >>> ewlinregression(_, 0.01, 100.0) + dict{ + R2: 0.34201204121343765 + fit: timeseries{ + start: 2021-10-14 14:03:00 +0100 IST + end: 2021-10-14 14:08:00 +0100 IST + 2021-10-14 14:03:00 +0100 IST: 4.5425229615148055e-05 + 2021-10-14 14:04:00 +0100 IST: 4.4509288322558405e-05 + 2021-10-14 14:05:00 +0100 IST: 4.359334702641604e-05 + 2021-10-14 14:06:00 +0100 IST: 4.267740573382639e-05 + 2021-10-14 14:07:00 +0100 IST: 4.176146444123674e-05 + 2021-10-14 14:08:00 +0100 IST: 4.0845523145094376e-05 + } + intercept: 24.94748623552414 + slope: -1.526568822982724e-08 + } + +String manipulation +^^^^^^^^^^^^^^^^^^^ + +.. _strToUpper: + +strToUpper +********** + +:guilabel:`Added in revision 1` + +Function ``strToUpper`` returns uppercase version of given :ref:`str`. + +* The first and only parameter is a :ref:`str` to convert to uppercase + +.. code:: aqlp + + >>> strToUpper("ToUpper") + "TOUPPER" + +.. _strToLower: + +strToLower +********** + +:guilabel:`Added in revision 1` + +Function ``strToLower`` returns lowercase version of given :ref:`str`. + +* The first and only parameter is a :ref:`str` to convert to lowercase + +.. code:: aqlp + + >>> strToLower("ToLower") + "TOLOWER" + +.. _strContains: + +strContains +*********** + +:guilabel:`Added in revision 1` + +Function ``strContains`` returns whether the first :ref:`str` contains the second :ref:`str`. + +* Both arguments to the function are :ref:`str` + +.. code:: aqlp + + >>> strContains("thatistext", "is") + true + +.. _strCount: + +strCount +******** + +:guilabel:`Added in revision 1` + +Function ``strCount`` returns the number of occurrences of the second :ref:`str` in the first :ref:`str`. + +* Both arguments to the function are :ref:`str` + +.. code:: aqlp + + >>> strCount("tertarter", "te") + 2 + +.. _strIndex: + +strIndex +******** + +:guilabel:`Added in revision 1` + +Function ``strIndex`` returns the index of the first occurrence of the second :ref:`str` in the first :ref:`str`, and -1 if it is not present. + +* Both arguments to the function are :ref:`str` + +.. code:: aqlp + + >>> strIndex("thatistext", "is") + 4 + +.. _strReplace: + +strReplace +********** + +:guilabel:`Added in revision 1` + +Function ``strReplace`` returns a copy of the first :ref:`str`, where occurrences of the second :ref:`str` are replaced by the third :ref:`str`. + +* The three arguments to the function are :ref:`str` + +.. code:: aqlp + + >>> strReplace("thatistext", "is", "was") + "thatwastext" + +.. _strHasPrefix: + +strHasPrefix +************ + +:guilabel:`Added in revision 1` + +Function ``strHasPrefix`` returns whether the first :ref:`str` starts with the second :ref:`str`. + +* Both arguments to the function are :ref:`str` + +.. code:: aqlp + + >>> strHasPrefix("thatistext", "is") + false + >>> strHasPrefix("thatistext", "that") + true + +.. _strHasSuffix: + +strHasSuffix +************ + +:guilabel:`Added in revision 1` + +Function ``strHasSuffix`` returns whether the first :ref:`str` ends with the second :ref:`str`. + +* Both arguments to the function are :ref:`str` + +.. code:: aqlp + + >>> strHasSuffix("thatistext", "xt") + true + +.. _strSplit: + +strSplit +******** + +:guilabel:`Added in revision 1` + +Function ``strSplit`` returns a :ref:`timeseries` of :ref:`str`. The function splits the first :ref:`str` into substrings, separated by the second :ref:`str`. + +* Both arguments to the function are :ref:`str` + +.. code:: aqlp + + >>> strSplit("that./is.text", "./") + timeseries{ + 0000-00-00 00:00:00.000000001: "that" + 0000-00-00 00:00:00.000000002: "is.text" + } + +.. _strCut: + +strCut +****** + +:guilabel:`Added in revision 4` + +Function ``strCut`` returns the portion of a :ref:`str` between two indexes (excluding ending index). + +Negative indexes start from the end of the input :ref:`str`. + +* The first argument (:ref:`str`) is the string from which the portion is returned +* The second argument (:ref:`num`) is the starting index +* The third argument (:ref:`num`) is the ending index + +.. code:: aqlp + + >>> strCut("0123456789", 1, 4) + 123 + >>> strCut("0123456789", -8, -2) + 234567 + >>> strCut("abcd", 1, 3) + bc + +.. _reFindAll: + +reFindAll +********* + +:guilabel:`Added in revision 1` + +Function ``reFindAll`` returns a :ref:`timeseries` of :ref:`str` which contains matches of the second :ref:`str` (regex) in the first :ref:`str`. + +* Both arguments to the function are :ref:`str` + +.. code:: aqlp + + >>> reFindAll("i am a string with text", "i[a-z]+") + timeseries{ + 0000-00-00 00:00:00.000000001: "ing" + 0000-00-00 00:00:00.000000002: "ith" + } + +.. _reMatch: + +reMatch +******* + +:guilabel:`Added in revision 1` + +Function ``reMatch`` returns whether the first :ref:`str` contains matches of the second :ref:`str` (regex). + +* Both arguments to the function are :ref:`str` + +.. code:: aqlp + + >>> reMatch("i am a string with text", "i[a-z]+") + true + +.. _reFindCaptures: + +reFindCaptures +************** + +:guilabel:`Added in revision 1` + +Function ``reFindCaptures`` returns a :ref:`timeseries` of :ref:`str` lists. Each list contains the full match followed by each capture + +* Both arguments to the function are :ref:`str`. The first one is the :ref:`str` to match, and the second is the regular expression + +.. code:: aqlp + + >>> reFindCaptures("foobarbaztootartaz", "foo") + timeseries{ + 0000-00-00 00:00:00.000000001: ["foo"] + } + >>> reFindCaptures("foobarbaztootartaz", "(foo)") + timeseries{ + 0000-00-00 00:00:00.000000001: ["foo", "foo"] + } + >>> reFindCaptures("foobarbaztootartaz", "f(oo)") + timeseries{ + 0000-00-00 00:00:00.000000001: ["foo", "oo"] + } + >>> reFindCaptures("foobarbaztootartaz", "(oo)") + timeseries{ + 0000-00-00 00:00:00.000000001: ["oo", "oo"] + 0000-00-00 00:00:00.000000002: ["oo", "oo"] + } + >>> reFindCaptures("foobarbaztootartaz", "[ft](oo)") + timeseries{ + 0000-00-00 00:00:00.000000001: ["foo", "oo"] + 0000-00-00 00:00:00.000000002: ["too", "oo"] + } + >>> reFindCaptures("foobarbaztootartaz", "([ft])(oo)") + timeseries{ + 0000-00-00 00:00:00.000000001: ["foo", "f", "oo"] + 0000-00-00 00:00:00.000000002: ["too", "t", "oo"] + } + >>> reFindCaptures("foobarbaztootartaz", "[ft](oo).*(az)") + timeseries{ + 0000-00-00 00:00:00.000000001: ["foobarbaztootartaz", "oo", "az"] + } + +CLI-only Functions +^^^^^^^^^^^^^^^^^^^^^ + +.. warning:: + + The functions described in this section can only be used in CLI. They cannot be called from a service + or through a Web interface. + +.. _help: + +help +**** + +:guilabel:`Added in revision 1` + +Function ``help`` returns the help of all filters and functions as a formatted :ref:`str`. + +.. code:: aqlp + + >>> help() + # Functions + ## now + - Function `now` returns the current time. Time is constant across all the script, so that all operations and queries have the same reference time + [...] + +.. _dump: + +dump +**** + +:guilabel:`Added in revision 1` + +Function ``dump`` attempts to dump variables from the interpreter into a file. + +* The first and only argument is the path to the file (:ref:`str`) + +.. code:: aqlp + + >>> let myVar = 2 + >>> dump("file.dump") + +.. _load: + +load +**** + +:guilabel:`Added in revision 1` + +Function ``load`` attempts to load variables into the interpreter from a file. + +* The first and only argument is the path to the file (:ref:`str`) + +.. code:: aqlp + + >>> load("file.dump") + true + >>> myVar + 2 + >>> let myVar = 5 + >>> if load("file.dump") { + ... myVar + ... } + 2 + +.. _plot: + +plot +**** + +:guilabel:`Added in revision 1` + +Function ``plot`` plots a :ref:`timeseries` or :ref:`dict` of :ref:`num` values. + +* The first argument is the :ref:`timeseries` or :ref:`dict` of :ref:`num` values to plot +* The second argument (optional) is the path (:ref:`str`) to the image PNG file to write the plot to. If not specified, defaults to ``plot.png`` + +.. code:: aqlp + + >>> plot(myTimeseriesOrDict, "myplotimg.png") diff --git a/_sources/doc/language_reference.rst.txt b/_sources/doc/language_reference.rst.txt new file mode 100644 index 0000000..f28f727 --- /dev/null +++ b/_sources/doc/language_reference.rst.txt @@ -0,0 +1,1669 @@ +Quick overview +-------------- + +The syntax in AQL is similar to that of scripting languages. However, it is designed to be used like +a query language. Its types are specifically designed to match the data structures of the CloudVision +database. + +In interactive mode, each statement is executed independently, and therefore each of their values is +printed when run. However, when running an AQL script with multiple statements from the CloudVision +dashboard or from a file, the only displayed value is the value of the script as a whole, which is the +value of the last statement in the script. + +Example in interactive mode: + +.. code-block:: aqlp + + >>> let a = 1 + >>> a + 1 + >>> a + 2 + 3 + +Running it in one go: + +.. code-block:: aql + + let a = 1 + a + a+2 + +This script simply returns `3`. + +The main features of the language are: + +* Queries: quickly fetch data from the CloudVision database +* Filters: efficiently format and extract the appropriate data from the query results + +Example: + +.. code-block:: aql + + # This first statement performs a query to get aggregate data for each interface of device SSJ123456 + let hardwareAggs = `analytics:/Devices/SSJ123456/versioned-data/interfaces/data/*/aggregate/hardware/xcvr/1m` + + # This statement uses filters to extract the average temperature for each interface + let temperaturePerIntf = hardwareAggs | map((_value | field("temperature") | field("avg"))[0]) + + # This last statement computes the average temperature across all the interfaces in the device and is the + # return value of the script + mean(temperaturePerIntf) + +.. raw:: html + + + + +Basic statements +---------------- + +Statements are separated by new lines. +Comments start with a :code:`#` so anything following :code:`#` is not part of a statement. + +.. code-block:: aqlp + + >>> 1 + 2 + 3 + >>> 1 * (2+1) / (2*5) + 0.3 + +Values can be assigned into a variable using the ``let`` keyword. + +.. code-block:: aqlp + + >>> let myVar1 = 9 % 5 # variable myVar1 contains 4 + +The last statement's value is stored in the metavariable ``_``. However, some statements like +variable assignments do not have a value, so they don't change the value of ``_``. + +.. code-block:: aqlp + + >>> _ + 0.3 + +Variables can be used in expressions. + +.. code-block:: aqlp + + >>> 5 * _ + 12.4 + myVar1 + -1 - 1 + 15.9 + >>> _ + 1 + 16.9 + +Variable names must begin with a letter (lower or uppercase). The rest of the name can contain letters +(lower or uppercase), digits, and underscores. + +.. warning:: + + Variable names that are prefixed with an underscore are metavariables and are set by the interpreter. + These cannot be reassigned (read-only) but they can be used. + + More details about metavariables defined by named wildcards (````) in the `Named wildcards <#namedwildcards>`_ + section of this document. + +.. code-block:: aqlp + + >>> let myVar_Name2 = 1 + >>> myVar_Name2 + 1 + >>> let someData = `:/Devices/some/data/path` + >>> _d + JPE123456 + >>> let _metavar = 12 + error: input:1:1: illegal variable name: _metavar + +Types +----- + +.. _num: + +num +^^^ + +The ``num`` type is a ``float64`` and is the only numerical type. AQL does not have a native integer type. + +This type can be defined through literals, either an integer or a floating-point value: + +.. code-block:: aqlp + + >>> let i = 12 + >>> let j = 15.42 + >>> i+j + 27.42 + +.. _bool: + +bool +^^^^ + +The ``bool`` type is a boolean and can either be true or false. + +The syntax of its literal is either the ``true`` or ``false`` keyword. + +.. code-block:: aqlp + + >>> let b = true + >>> b && false + false + +.. _str: + +str +^^^ + +The ``str`` type is a string of characters. + +The syntax of its literal is any string of character surrounded with double-quotes. To insert a double-quote +within the literal, it can be prefixed with a backslash ``\\``. +Single-quote strings are not supported in AQL. + +All types can be cast to ``str``. + +.. code-block:: aqlp + + >>> "Don't \"panic\"!" + Don't "panic"! + >>> str(12.0) # this is a cast from num to str + 12 + >>> str(12.1) + 12.1 + +.. _time: + +time +^^^^ + +The ``time`` type holds a timestamp. It is the key type in the :ref:`timeseries` type returned by queries. + +There are no literals for ``time``, but it can be cast from a :ref:`str` following the syntax described in RFC 3339. + +.. code-block:: aqlp + + >>> let t = time("2006-01-02T15:04:05+07:00") + >>> t + 2006-01-02 15:04:05 +0700 +0700 + >>> t + 15s + 2006-01-02 15:04:20 +0700 +0700 + + +.. _duration: + +duration +^^^^^^^^ + +The ``duration`` type defines a time interval. It can be used to define a time range of data to get +in queries, and it can be added to or subtracted from :ref:`time` values. + +The syntax of its literal is a signed (or not) sequence of decimal numbers followed by a unit suffix. +It can also be composed of multiple values in different time units: ``300ms``, ``-1.5h``, ``2h45m``. + +Valid time units are: + +* ``ns`` (nanosecond) +* ``us`` (microsecond) +* ``ms`` (millisecond) +* ``s`` (second) +* ``m`` (minute) +* ``h`` (hour) + +.. code-block:: aqlp + + >>> 5h30ms + 5h0m0.03s + >>> 7 * 24h # week + 168h0m0s + >>> time("2006-01-02T15:04:05+07:00") + 5h15s + 2006-01-02 15:04:20 +0700 +0700 + + +.. _type: + +type +^^^^ + +The ``type`` type holds type information. Any value can be cast to ``type`` to know its type. + +The syntax of its literal is any type name without any quotes or delimiter. + +.. code-block:: aqlp + + >>> let a = 2 + >>> type(a) # This is a cast to type `type` + num + >>> type("Hello World!") + str + >>> type(str) + type + >>> type("Don't panic!") == bool + false + +.. _timeseries: + +timeseries +^^^^^^^^^^ + +The ``timeseries`` type is a list of values (of any type), indexed by timestamps (:ref:`time` values). +Its values can be accessed either by :ref:`num` index or :ref:`time` index. If there is no exact match for the +specified :ref:`time`, accessing its value will return the latest entry before that time. + +.. note:: + + There are no literals for ``timeseries`` and they cannot be manually created. It can be returned by some + functions (see the documentation for :doc:`Standard Library ` functions), and all AQL queries return a ``timeseries`` + (which can be contained in a :ref:`dict`, see sections about `Wildcards <#wcards>`_). + +.. code-block:: aqlp + + >>> let a = `analytics:/Devices/JPE17191574/versioned-data/interfaces/data/Ethernet50/aggregate/hardware/xcvr/1m`[5m] | field("temperature") | field("avg") + >>> a + timeseries{ + start: 2021-10-26 14:32:17.167535 +0100 IST + end: 2021-10-26 14:37:17.167535 +0100 IST + 2021-10-26 14:32:00 +0100 IST: 26.77301344308594 + 2021-10-26 14:33:00 +0100 IST: 26.78515625 + 2021-10-26 14:34:00 +0100 IST: 26.64152704258496 + 2021-10-26 14:35:00 +0100 IST: 26.68106989897461 + 2021-10-26 14:36:00 +0100 IST: 26.76746009308496 + 2021-10-26 14:37:00 +0100 IST: 26.78515625 + } + >>> a[0] + 26.77301344308594 + >>> a[time("2021-10-26T14:34:05+01:00")] + 26.64152704258496 + + +.. _dict: + +dict +^^^^ + +The ``dict`` type is a collection of key/value pairs (map). + +.. note:: + + There are no literals for ``dict`` but an empty ``dict`` can be created using the :ref:`newDict` function, and + its fields can be set using the bracket operator assignments or various filters such as :ref:`setFields`. + +.. code-block:: aqlp + + >>> let d = newDict() + >>> d + dict{} + >>> d["key1"] = 1 + >>> d["key2"] = 2 + >>> d + dict{ + key1: 1 + key2: 2 + } + >>> d | setFields("key2", 0, "key3", 3) + dict{ + key1: 1 + key2: 0 + key3: 3 + } + + +.. _unknown: + +unknown +^^^^^^^ + +The ``unknown`` type is applied to any value that is not a standard AQL type. Some of the data in CloudVision +can be of a type that does not match any of the native AQL types. There is limited support +to extract and use these values (they can be used in :ref:`dict` keys and values). + +.. note:: + + There are no literals but some values of that type can be created using the :ref:`complexKey` function. + See sections `Complex path elements <#complex-pathelts>`_ and :ref:`complexKey`. + +Language keywords +----------------- + +Here is a full list of the language keywords in AQL: + +* ``let`` +* ``for`` +* ``if`` +* ``else`` +* ``while`` +* ``in`` +* :ref:`true` +* :ref:`false` +* :ref:`num` +* :ref:`bool` +* :ref:`str` +* :ref:`time` +* :ref:`duration` +* :ref:`type` +* :ref:`timeseries` +* :ref:`dict` +* :ref:`unknown` + +Language Operators +------------------ + +From lowest to highest precedence: + +* ``=`` (assignment) +* ``?`` ``:`` (ternary operations) +* ``||`` (logical OR) +* ``&&`` (logical AND) +* ``!=`` ``==`` (equality check operators) +* ``<`` ``<=`` ``>=`` ``>`` (comparison operators) +* ``+`` ``-`` (addition/concatenation and subtraction) +* ``*`` ``/`` ``%`` (multiplication, division, modulo) +* ``|`` (pipe for filters) +* ``^`` (power) +* ``!`` (logical NOT) +* ``[]`` (access values at a specific index/key/time in :ref:`timeseries`/:ref:`dicts `) + +Comparisons +----------- + +Two values of the same type can be compared. + +Equality operators (``==`` and ``!=``) work with values of any type, even :ref:`dict` and :ref:`timeseries` (but +both values must be of the same type) + +.. code-block:: aqlp + + >>> true == false + false + >>> let s = "myString" + >>> "myString" == s + true + >>> 2 != 3 + true + +Values can also be compared using the greater and lower operators (``<``, ``<=``, ``>``, ``>=``). Both compared +values must have the same type, either :ref:`str` (ASCII order), :ref:`num`, :ref:`time` (before or after), or :ref:`duration`. + +.. code-block:: aqlp + + >>> myVar1 + 4 + >>> myVar1 > 4 + false + >>> myVar1 >= 4 + true + myVar1 == 4 + true + >>> let myBooleanVar = myVar1 + 1 <= 5 + >>> myBooleanVar + true + >>> "ab" < "ac" + true + + +Operations +---------- + +Boolean operations +^^^^^^^^^^^^^^^^^^ + +Boolean values can be used with ``!`` (NOT), ``&&`` (AND), and ``||`` (OR) for boolean logic + +.. code-block:: aqlp + + >>> myBooleanVar + true + >>> myBooleanVar && 1 > 2 + false + >>> !(myBooleanVar && 1 > 2) && !_ || 1 > 2 + true + + +String concatenations +^^^^^^^^^^^^^^^^^^^^^ + +Strings can be concatenated with the ``+`` operator. + +.. code-block:: aqlp + + >>> "Hello " + "world" + "!" + Hello world! + + +Additions and Subtractions +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Additions (``+``) and subtractions (``-``) can be performed with the following type combinations: + +* ``num + num``: returns a :ref:`num` +* ``num - num``: returns a :ref:`num` +* ``time - time``: returns a :ref:`duration` +* ``time + duration``: returns a :ref:`time` +* ``time - duration``: returns a :ref:`time` + +.. code-block:: aqlp + + >>> 2+3.4 + 5.4 + >>> let n = now() # now() returns the current time as a `time` value + >>> n + 2021-10-26 15:19:56.184361 +0100 + >>> let n2 = n - 15m + >>> n2 + 2021-10-26 15:04:56.184361 +0100 + >>> n - n2 + 15m0s + >>> n2 + 15*60s == n + true + + +Multiplications and Divisions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Multiplications (``*``) and divisions (``/``) can be performed with the following type combinations: + +* ``num * num``: returns a :ref:`num` +* ``num / num``: returns a :ref:`num` +* ``num * duration``: returns a :ref:`duration` +* ``duration / num``: returns a :ref:`duration` + +.. code-block:: aqlp + + >>> 3*3 + 9 + >>> 4.4/4 + 1.1 + >>> 3*60s + 3m0s + >>> 3m/180 + 1s + + +Modulo +^^^^^^ + +The modulo (``%``) operator returns the remainder of a division. It can only be used with two :ref:`num` values. + +.. code-block:: aqlp + + >>> 10 % 3 + 1 + + +Power +^^^^^ + +The power (``^``) operator returns ``a`` to the power of ``b``. It can only be used with two :ref:`num` values. + +.. code-block:: aqlp + + >>> 3^3 + 27 + +Typecasts +--------- + +It is possible to cast values of a certain type to another using the syntax ``typename(valueToCast)``. +Here is a typecast table defining which types can be cast to which other types. + +.. list-table:: + :header-rows: 1 + + * - FROM / TO + - num + - bool + - str + - time + - duration + - type + - timeseries + - dict + - unknown + * - num + - **YES** + - **YES** + - **YES** + - **YES** + - **YES** + - **YES** + - NO + - NO + - NO + * - bool + - **YES** + - **YES** + - **YES** + - NO + - NO + - **YES** + - NO + - NO + - NO + * - str + - **YES** + - **YES** + - **YES** + - **YES** + - **YES** + - **YES** + - NO + - NO + - NO + * - time + - **YES** + - **YES** + - **YES** + - **YES** + - NO + - **YES** + - NO + - NO + - NO + * - duration + - **YES** + - NO + - **YES** + - NO + - **YES** + - **YES** + - NO + - NO + - NO + * - type + - NO + - NO + - **YES** + - NO + - NO + - **YES** + - NO + - NO + - NO + * - timeseries + - NO + - NO + - **YES** + - NO + - NO + - **YES** + - **YES** + - NO + - NO + * - dict + - NO + - NO + - **YES** + - NO + - NO + - **YES** + - NO + - **YES** + - NO + * - unknown + - NO + - NO + - **YES** + - NO + - NO + - **YES** + - NO + - NO + - NO + +.. note:: + + * For all casts between :ref:`num`, :ref:`duration`, and :ref:`time`, the time unit is the nanosecond + * Cast from :ref:`str` to :ref:`num` supports float and integer notation but also scientific (1e+2, 15e-3 etc.) + * Casts between :ref:`time` and :ref:`str` follow the syntax defined in + `RFC 3339 `_. + +.. code:: aqlp + + >>> num("12") + 12 + >>> str(11+1) + "a" + 12a + >>> type("42") + str + >>> type(type("42")) + type + +As described in the section about type :ref:`type`, type names can be used as :ref:`type` literals to perform +type-assertions. + +.. code:: aqlp + + >>> type(false) == str + false + >>> type(false) == bool + true + +Queries +------- + +AQL can fetch data from the CloudVision database by using queries. The general syntax is the following: + +.. code:: aql + + `datasetType/datasetName:/path/to/data`[queryParameter] + +Dataset +^^^^^^^ + +The dataset section of the query is split into two parts with a forward slash (`/`). The first part +is the dataset type (e.g. ``device``, ``app``, ``config``...). + +The second part is the dataset name. + +Example: + +.. code:: aql + + `user/johndoe:/path/to/data`[queryParameter] + +If unspecified, the dataset type will default to :code:`device`: + +.. code:: aql + + `JPE123456:/path/to/data`[queryParameter] + +Path +^^^^ + +The path section of the query is the path to the data in the CloudVision database, and each path +element is separated by a forward slash (`/`). + +.. code:: aql + + `analytics:/Devices/JPE123456/versioned-data/interfaces/data/Ethernet1/rates`[queryParameter] + +A query with a fully specified path like the above will always return a :ref:`timeseries`. + +The value associated with each specific :ref:`time` in the :ref:`timeseries` is a :ref:`dict` containing all the +key-value pairs updated at that path, at that specific :ref:`time`. + + +.. Sphinx refuses to call a label "wildcards", so this is called "wcards" + +.. _wcards: + +Wildcards +^^^^^^^^^^^ + +If a path element or the dataset name (dataset type can not be wildcarded) is replaced with a simple +star sign (``*``), called a wildcard, the query fetches the data at all the paths matching this wildcarded path. + +Example: In the previous section, the query was fetching the interface rates for device "JPE123456", +and interface "Ethernet1". This example gets the interface rates for all interfaces of device "JPE123456". + +.. code:: aql + + `analytics:/Devices/JPE123456/versioned-data/interfaces/data/*/rates`[queryParameter] + +Queries containing a wildcard do not return a ``timeseries``, but a ``dict``. Its keys are the path +element values matching the wildcard (in the example above, the interface names). The ``dict`` values +are the ``timeseries`` that would have been returned if querying the same path with the wildcard +replaced with each possible key. + +.. code:: aqlp + + >>> `analytics:/Devices/JPE123456/versioned-data/interfaces/data/Ethernet1/rates`[0] + timeseries{ + start: 2021-10-26 16:12:46.870252166 +0100 IST + end: 2021-10-26 16:12:47.674314 +0100 IST + 2021-10-26 16:12:46.870252166 +0100 IST: dict{ + inMulticastPkts: 0.5000081856910986 + inOctets: 61.50100684000513 + outMulticastPkts: 0 + outOctets: 0 + } + } + >>> `analytics:/Devices/JPE123456/versioned-data/interfaces/data/*/rates`[0] + dict{ + Ethernet1: timeseries{ + start: 2021-10-26 16:13:16.870363498 +0100 IST + end: 2021-10-26 16:13:34.615865 +0100 IST + 2021-10-26 16:13:16.870363498 +0100 IST: dict{ + outMulticastPkts: 0 + outOctets: 0 + } + 2021-10-26 16:13:26.870256382 +0100 IST: dict{ + inMulticastPkts: 0.5000009149562546 + inOctets: 61.50011253961932 + } + } + Ethernet2: timeseries{ + start: 2021-10-26 16:13:26.870256382 +0100 IST + end: 2021-10-26 16:13:34.615865 +0100 IST + 2021-10-26 16:13:26.870256382 +0100 IST: dict{ + inMulticastPkts: 0.10000018299125094 + inOctets: 38.50007045163161 + inUcastPkts: 0.20000036598250187 + outMulticastPkts: 0.10000018299125094 + outOctets: 12.80002342288012 + } + } + +A query can also contain multiple wildcards, which will result in several levels of nested :ref:`dicts `, +with :ref:`timeseries` at the bottom level. + +This example fetches the same data as before, but for all interfaces of all devices, using two +wildcards: + +.. code:: aqlp + + >>> `analytics:/Devices/*/versioned-data/interfaces/data/*/rates`[0] + dict{ + JPE123456: dict{ + Ethernet1: timeseries{ + start: 2021-10-26 16:13:16.870363498 +0100 IST + end: 2021-10-26 16:13:34.615865 +0100 IST + 2021-10-26 16:13:16.870363498 +0100 IST: dict{ + outMulticastPkts: 0 + outOctets: 0 + } + 2021-10-26 16:13:26.870256382 +0100 IST: dict{ + inMulticastPkts: 0.5000009149562546 + inOctets: 61.50011253961932 + } + } + Ethernet2: timeseries{ + start: 2021-10-26 16:13:26.870256382 +0100 IST + end: 2021-10-26 16:13:34.615865 +0100 IST + 2021-10-26 16:13:26.870256382 +0100 IST: dict{ + inMulticastPkts: 0.10000018299125094 + inOctets: 38.50007045163161 + inUcastPkts: 0.20000036598250187 + outMulticastPkts: 0.10000018299125094 + outOctets: 12.80002342288012 + } + } + } + JPE654321: dict{ + Ethernet1: timeseries{ + start: 2021-10-26 16:13:16.870363498 +0100 IST + end: 2021-10-26 16:13:34.615865 +0100 IST + 2021-10-26 16:13:16.870363498 +0100 IST: dict{ + outMulticastPkts: 0 + outOctets: 0 + } + 2021-10-26 16:13:26.870256382 +0100 IST: dict{ + inMulticastPkts: 0.50000037628384 + inOctets: 67.638619033792 + } + } + Ethernet2: timeseries{ + start: 2021-10-26 16:13:26.870256382 +0100 IST + end: 2021-10-26 16:13:34.615865 +0100 IST + 2021-10-26 16:13:26.870256382 +0100 IST: dict{ + inMulticastPkts: 0.10000027274982 + inOctets: 33.478329283748833 + inUcastPkts: 0.20000036598250187 + outMulticastPkts: 0.100000432767384 + outOctets: 12.828728378483 + } + } + } + +For a dataset wildcard, the result is built with the same structure. The syntax is as follows: + +.. code:: aql + + `user/*:/some/path`[queryParameter] # This will get data for all `user` datasets + `*:/some/path`[queryParameter] # This will get data for all `device` datasets + + +.. _complex-pathelts: + +Complex path elements +^^^^^^^^^^^^^^^^^^^^^ + +Most paths in the database are made of string path elements. In AQL, they are natively handled and +are separated with slashes in queries. However, some paths can contain path elements of different +types, some of which don't exist in AQL. AQL, however, supports some of them using the curly +brackets syntax. + +Numerical value +*************** + +A numerical literal can be used between the curly brackets, and will produce an int path element if +the literal is an integer literal, and a float path element when it has a decimal part (nil or not). + +.. code:: aqlp + + >>> `myDataset:/foo/{12}/bar` # int path element + >>> `myDataset:/foo/{12.}/bar` # float path element + >>> `myDataset:/foo/{12.0}/bar` # float path element + >>> `myDataset:/foo/{12.35}/bar` # float path element + +Boolean value +************* + +A boolean literal can be used between the curly brackets. + +.. code:: aqlp + + >>> `myDataset:/foo/{true}/bar` # bool (true) path element + >>> `myDataset:/foo/{false}/bar` # bool (false) path element + >>> `myDataset:/foo/true/bar` # string path element + >>> `myDataset:/foo/{"true"}/bar` # string path element (identical to the previous one) + +String value +************ + +A string literal can be used between the curly brackets. This is mostly useful for path elements that +contain a slash + +.. code:: aqlp + + >>> `myDataset:/foo/{"my string value"}/bar` # string path element + >>> `myDataset:/foo/{"my/string/with/slashes"}/bar` # string path element containing slashes + >>> `myDataset:/foo/my\/string\/with\/slashes/bar` # identical to the previous one + +Map value +********* + +A map can be input using the JSON syntax (curly brackets and comma-separated colon-linked pairs). +JSON does not know the difference between floats and ints, so a numerical value with a nil decimal +part will be interpreted as an int, and one with a non-nil decimal part will be interpreted as a +float. Can contain nested lists and maps. + +.. code:: aqlp + + >>> `myDataset:/foo/{"key": 1.0}/bar` # map("key": int(1)) path element + >>> `myDataset:/foo/{"key": 1}/bar` # map("key": int(1)) path element + >>> `myDataset:/foo/{"key": 1.1}/bar` # map("key": float(1.1)) path element + >>> `myDataset:/foo/{"key": "val", "keyb": true}/bar` # map("key": str("val"), "keyb": bool(true)) + +List value +********** + +A list can be input using the JSON syntax (square brackets and comma-separated values). JSON does +not know the difference between floats and ints, so a numerical value with a nil decimal part will +be interpreted as an int, and one with a non-nil decimal part will be interpreted as a float. Can +contain nested lists and maps + +.. code:: aqlp + + >>> `myDataset:/foo/[1.0, 1, 1.1]/bar` # list(int(1), int(1), float(1.1)) path element + >>> `myDataset:/foo/[true, "str", {"subkey": "subval"}, [1]]/bar` # list(bool(true), str("str"), map("subkey": str("subval")), list([int(1)])) + +Query parameter +^^^^^^^^^^^^^^^ + +The query parameter is specified within the square brackets attached to the query. It determines +the amount (time range or number of updates) of data to fetch. + +The parameter must be written as a :ref:`num` or :ref:`duration` literal. It cannot use the value of a variable. + +No parameter +************ + +If the parameter is not specified, it is equivalent to specifying ``0`` within the brackets. In that case, +the query will only return the state of data at the current time. + +This :ref:`timeseries` can contain multiple updates if the keys at this path were last update at different times. + +In the example below, keys were last updated in 3 different updates, so the timeseries contains 3 updates. + +.. code:: aqlp + + >>> `analytics:/Devices/JPE12345/path/to/some/interface/data` + timeseries { + start: 2021-10-26 16:13:26 +0100 + end: 2021-10-26 16:13:34 +0100 + 2021-10-26 16:13:26 +0100 IST: dict{ + key4: 5 + key5: 6 + } + 2021-10-26 16:13:29 +0100 IST: dict{ + key3: 2 + } + 2021-10-26 16:13:34 +0100 IST: dict{ + key1: 2 + key2: 1 + } + } + +.. admonition:: Note: Merging the result + + When getting only the current state (not specifying any parameter), it is common + practice to use the :ref:`merge` function, which will turn a :ref:`timeseries` of :ref:`dicts ` into a simple :ref:`dict`, + containing the latest value for each possible key. This allows for direct manipulation of data. + +.. code:: aqlp + + >>> merge(`analytics:/Devices/JPE12345/path/to/some/interface/data`) + dict{ + key1: 2 + key2: 1 + key3: 2 + key4: 5 + key5: 6 + } + +.. warning:: + + Do not confuse the query parameter with the bracket operator that accesses a specific update + in an existing :ref:`timeseries`. + + In the following example, the first bracket expression is the query parameter, and the second is the + index of the value to get in the resulting :ref:`timeseries`. + + .. code:: aqlp + + >>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[0][0] + dict{ + key4: 5 + key5: 6 + } + + If you want to use the "index-access" bracket operator and not specify a query parameter, you must either + explicitly define the query parameter before, or surround the query with parentheses. + + .. code:: aqlp + + >>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[0] + timeseries { + start: 2021-10-26 16:13:26 +0100 + end: 2021-10-26 16:13:34 +0100 + 2021-10-26 16:13:26 +0100 IST: dict{ + key4: 5 + key5: 6 + } + 2021-10-26 16:13:29 +0100 IST: dict{ + key3: 2 + } + 2021-10-26 16:13:34 +0100 IST: dict{ + key1: 2 + key2: 1 + } + } + >>> `analytics:/Devices/JPE12345/path/to/some/interface/data`["key4"] + error: input:1:1: bracket selector of query can only get a num or duration, got str + >>> (`analytics:/Devices/JPE12345/path/to/some/interface/data`)["key4"] + error: input:1:16: operator [] applied to timeseries needs a value of type num or time + >>> (`analytics:/Devices/JPE12345/path/to/some/interface/data`)[0] + dict{ + key4: 5 + key5: 6 + } + >>> (`analytics:/Devices/JPE12345/path/to/some/interface/data`)[0]["key4"] + 5 + >>> merge(`analytics:/Devices/JPE12345/path/to/some/interface/data`)["key4"] + 5 + + +Number of updates +***************** + +If the square brackets contain a :ref:`num` literal, this :ref:`num` defines what number ``n`` of updates to get. +The query will fetch the ``n`` latest updates at this path, with each update corresponding to an entry +in the resulting :ref:`timeseries`. However, the length of the timeseries can be superior to ``n``, because +the query also gets the "state" of data before the ``n`` updates, i.e. the last update for each key at this +path before the ``n`` updates. + +In the example below, the query requests 3 updates. However, the :ref:`timeseries` returned has a length of 5. +This is because the oldest update of the 3 only updates the value of keys ``key4`` and ``key5``, but not ``key1``, +``key2``, and ``key3``. Therefore the query also returns the latest update before it for each of these keys. +Here, there are two of these "state" updates: one updates both ``key1`` and ``key2``, and the other updates ``key3``. + +.. code:: aqlp + + >>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[3] + timeseries { + start: 2021-10-26 16:13:16 +0100 + end: 2021-10-26 16:13:34 +0100 + 2021-10-26 16:13:16 +0100 IST: dict{ + key1: 1 + key2: 2 + } + 2021-10-26 16:13:23 +0100 IST: dict{ + key3: 1 + } + 2021-10-26 16:13:26 +0100 IST: dict{ + key4: 5 + key5: 6 + } + 2021-10-26 16:13:29 +0100 IST: dict{ + key1: 2 + key3: 1 + } + 2021-10-26 16:13:34 +0100 IST: dict{ + key1: 2 + key2: 1 + } + } + +If the oldest of the 3 updates had updated all the keys stored at this path, there would not have been +any "state" update and the length of the :ref:`timeseries` would have been 3: + +.. code:: aqlp + + >>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[3] + timeseries { + start: 2021-10-26 16:13:26 +0100 + end: 2021-10-26 16:13:34 +0100 + 2021-10-26 16:13:26 +0100 IST: dict{ + key1: 5 + key2: 4 + key3: 3 + key4: 2 + key5: 1 + } + 2021-10-26 16:13:29 +0100 IST: dict{ + key1: 2 + key3: 1 + } + 2021-10-26 16:13:34 +0100 IST: dict{ + key1: 2 + key2: 1 + } + } + +Duration +******** + +If the square brackets contain a :ref:`duration` literal, this specifies the time range of data returned +by the query. + +The query will return all the updates that happened at this path during the last ``d`` duration, along +with the "state" updates, following the same rules as the number of updates. + +In the example below, the query fetches the latest 8 seconds of data. In this interval, three updates +happened, the oldest of which only updated ``key4`` and ``key5``, so the returned timeseries also contains +two older updates, which are the latest updates for ``key1``, ``key2`` and ``key3`` before ``now() - 8s``. + +.. code:: aqlp + + >>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[8s] + timeseries { + start: 2021-10-26 16:13:16 +0100 + end: 2021-10-26 16:13:34 +0100 + 2021-10-26 16:13:16 +0100 IST: dict{ + key1: 1 + key2: 2 + } + 2021-10-26 16:13:23 +0100 IST: dict{ + key3: 1 + } + 2021-10-26 16:13:26 +0100 IST: dict{ + key4: 5 + key5: 6 + } + 2021-10-26 16:13:29 +0100 IST: dict{ + key1: 2 + key3: 1 + } + 2021-10-26 16:13:34 +0100 IST: dict{ + key1: 2 + key2: 1 + } + } + +Fixed timestamps range +********************** + +It is also possible to use two timestamps separated with a colon (``:``). This syntax allows querying +data that was written between these timestamps, along with the state data from before the first +timestamp. + +Like for every query parameter, it is not possible to use a regular variable as one of the timestamps. +They have to be defined directly within the square brackets, or use an input metavariable (defined +outside of the AQL script scope). + +.. code:: aqlp + + >>> `analytics:/path/to/data`[time("2022-01-26T16:00:00+00:00"):time("2022-01-26T16:01:00+00:00")] + timeseries { + start: 2022-01-26 16:00:00 +0000 GMT + end: 2022-01-26 16:01:00 +0000 GMT + 2021-10-26 15:59:30 +0100 IST: dict{ + key1: 1 + } + 2021-10-26 16:00:00 +0100 IST: dict{ + key1: 2 + } + 2021-10-26 16:00:30 +0100 IST: dict{ + key1: 3 + } + 2021-10-26 16:01:00 +0100 IST: dict{ + key1: 4 + } + } + +Example with input variables: + +.. code:: aqlp + + >>> `analytics:/path/to/data`[_startTime:_endTime] + timeseries { + start: 2022-01-26 16:00:00 +0000 GMT + end: 2022-01-26 16:01:00 +0000 GMT + 2021-10-26 15:59:30 +0100 IST: dict{ + key1: 1 + } + 2021-10-26 16:00:00 +0100 IST: dict{ + key1: 2 + } + 2021-10-26 16:00:30 +0100 IST: dict{ + key1: 3 + } + 2021-10-26 16:01:00 +0100 IST: dict{ + key1: 4 + } + } + +Square bracket operator +----------------------- + +When applied to a collection (:ref:`dict` or :ref:`timeseries`), the square bracket operator allows access to +a specific value of that collection. + +Timeseries +^^^^^^^^^^ + +For a :ref:`timeseries`, the type specified within the square brackets can be either a :ref:`num` for access to +a specific numerical index (starts at 0) in the timeseries, or a :ref:`time`, for access to a the value at +a specific :ref:`time` (if there is no exact match, it will return the latest value before the specied :ref:`time`). + +When accessing a timeseries value using the square bracket operator, the interpreter sets the metavariables +``_bracketTime`` and ``_bracketIndex`` to the exact time and index associated with that value. + +The `num` index can be negative, in which case it starts from the end of the :ref:`timeseries` (index ``-1`` is the +last update) + +.. code:: aqlp + + >>> myTimeseries + timeseries { + start: 2021-10-26 16:13:16 +0100 + end: 2021-10-26 16:13:34 +0100 + 2021-10-26 16:13:16 +0100 IST: "val1" + 2021-10-26 16:13:23 +0100 IST: "val2" + 2021-10-26 16:13:26 +0100 IST: "val3" + 2021-10-26 16:13:29 +0100 IST: "val4" + 2021-10-26 16:13:34 +0100 IST: "val5" + } + >>> myTimeseries[time("2021-10-26T16:13:25+01:00")] + val2 + >>> _bracketTime + 2021-10-26 16:13:23 +0100 IST + >>> _bracketIndex + 1 + >>> myTimeseries[-2] + val4 + >>> _bracketTime + 2021-10-26 16:13:29 +0100 IST + >>> _bracketIndex + 3 + +Dict +^^^^ + +The square bracket operator allows access to the value associated with a specific key in a :ref:`dict`. +The key can be of any valid key type: + +* :ref:`num` +* :ref:`bool` +* :ref:`str` +* any value returned by the `complexKey` function (even if type is :ref:`unknown`) + + +.. code:: aqlp + + >>> let d = newDict() | setFields("key", 1, 2, 3, complexKey("{\"k\": \"v\"}"), 4) + >>> d + dict{ + 2: 3 + key: 1 + {"k":"v"}: 4 + } + >>> d[2] + 3 + >>> d["key"] + 1 + >>> d[complexKey("{\"k\": \"v\"}")] + 4 + +This operator also allows for setting values in the :ref:`dict`. + +.. code:: aqlp + + >>> let d = newDict() + >>> d["key"] = "value" + >>> d + dict{key: value} + +If/Else +------- + +AQL also supports ``if`` / ``else`` conditions. The syntax is as follows: + +.. code:: aql + + if condition { + # statements + } else { + # statements + } + +It is possible to write only the ``if`` block and not the ``else``. + +.. code:: aql + + if condition { + # statements + } + +Variables in AQL are not scoped. This means that variables defined within the scope of the ``if`` / ``else`` +can be accessed from outside. + +.. code:: aqlp + + >>> a + error: input:1:1: undeclared variable: a + >>> if 5 > 3 { + ... let a = 1 + ... } + >>> a + 1 + +The metavariable ``_`` is set even by statements within the scope of an ``if`` / ``else``. + +.. code:: aqlp + + >>> let a = 6 * 7 + >>> if a == 42 || a == 6 * 9 { + ... "a is the answer" + ... } else { + ... "a is not the answer" + ... } + >>> _ + a is the answer + +However, the ``if`` / ``else`` statement itself does not have a return value, like a variable assignment. +Therefore, this script will not return ``"a is not the answer"`` but will have no return value: + +.. code:: aql + + let a = 5 + if a == 42 || a == 6 * 9 { + "a is the answer" + } else { + "a is not the answer" + } + +To return this value at the end of the script, it is possible to just add a statement that simply +returns the ``_`` value. In that case, the script will return ``"a is not the answer"``: + +.. code:: aql + + let a = 5 + if a == 42 || a == 6 * 9 { + "a is the answer" + } else { + "a is not the answer" + } + _ + + +Ternary expressions +------------------- + +Ternary expressions allow to use conditions directly within an expression. The syntax is similar to +that of the C language. + +.. code:: aql + + condition ? valueIfConditionIsTrue : valueIfConditionIsFalse + +This can be used in any context that manipulates a value. + +.. code:: aqlp + + >>> let a = 2 + >>> let b = a < 3 ? "a lower than 3" : "a greater than 3" + >>> b + a lower than 3 + +Ternary expressions can be nested. + +.. code:: aqlp + + >>> let a = 2 + >>> let b = a > 0 ? a > 5 ? "big" : "small" : "negative" + >>> b + small + +Ternary expressions are mostly used within programmatic filters such as :ref:`map` (see section `Filters <#filters>`_), +because these filters only allow pure expressions, and statements such as ``if`` / ``else`` or variable +declarations statements cannot be used in their scope. + +.. code:: aqlp + + >>> let data = `analytics:/some/data/path`[16s] | field("avg") + >>> let threshold = 10 + >>> data | map(_value <= threshold ? _value : "forbidden") + timeseries { + start: 2021-10-26 16:13:16 +0100 + end: 2021-10-26 16:13:34 +0100 + 2021-10-26 16:13:16 +0100 IST: 5 + 2021-10-26 16:13:23 +0100 IST: forbidden + 2021-10-26 16:13:26 +0100 IST: 3 + 2021-10-26 16:13:29 +0100 IST: 10 + 2021-10-26 16:13:34 +0100 IST: forbidden + } + +Loops +----- + +AQL supports two kinds of loops: :ref:`for ` and :ref:`while `. + +.. _for-loop: + +For loop +^^^^^^^^ + +The for loop iterates over an existing collection (:ref:`dict` or :ref:`timeseries`). The syntax is as follows: + +.. code:: aql + + for k, v in collection { + # statements + } + +In the example above, ``k`` takes the current key (or timestamp if the collection is a :ref:`timeseries`), +and `v` its associated value at each iteration. ``k`` and ``v`` are not predefined names and can be named +any valid variable name by the user: + +.. code:: aqlp + + >>> let myDict = newDict() | setFields("k1", 1, "k2", 2) + >>> let s = "" + >>> for myKey, myValue in myDict { + ... let s = s + "{" + str(myKey) + ": " + str(myValue) + "}" + ... } + >>> s + {k1: 1}{k2: 2} + +It is possible to specify only one variable instead of both key and value. In this case, the variable +will only take the value at each iteration, and the timestamp/key will not be used. + +.. code:: aqlp + + >>> let myDict = newDict() | setFields("k1", 1, "k2", 2) + >>> let i = 0 + >>> for val in myDict { + ... let i = i + val + ... } + >>> i + 3 + +.. _while-loop: + +While loop +^^^^^^^^^^ + +While loops will iterate as long as the specified condition is :ref:`true `. The syntax is as follows: + +.. code:: aql + + while condition { + # statements + } + +Here is an example that computes the factorial of 6. + +.. code:: aqlp + + >>> let fact6 = 1 + >>> let a = 1 + >>> while a <= 6 { + ... let fact6 = fact6 * a + ... let a = a + 1 + ... } + >>> fact6 + 720 + +Functions +--------- + +The :doc:`Standard Library ` of AQL offers a wide range of functions. Each of them is documented in the +:doc:`Standard Library ` page. + +However, it is not possible to define new functions in AQL. + + +The syntax to call a standard library function is as follows: + +.. code:: aql + + functionName(argument1) + functionName(argument1, argument2) + +The number of arguments varies depending on the function. + +Some examples: + +.. code:: aqlp + + >>> let data = `analytics:/some/data/path`[10s] + >>> length(data) # length returns the length of a dict or timeseries + 5 + >>> let avgData = data | field("avg") + >>> avgData + timeseries { + start: 2021-10-26 16:13:25 +0100 + end: 2021-10-26 16:13:34 +0100 + 2021-10-26 16:13:25 +0100 IST: 5 + 2021-10-26 16:13:27 +0100 IST: 2 + 2021-10-26 16:13:29 +0100 IST: 3 + 2021-10-26 16:13:31 +0100 IST: 10 + 2021-10-26 16:13:33 +0100 IST: 1 + } + >>> mean(avgData) + 4.2 + +Filters +------- + +Filters are one of the most powerful features in AQL. They allow filtering, formatting, and refining of data +returned by queries very easily and much more efficiently than with loops and manual data manipulation. + +The AQL :doc:`Standard Library ` offers a wide range of filters, designed to adapt to the data structures of +the CloudVision database. Each of them is documented in the :doc:`Standard Library ` page. + +Filters can only be applied to a collection (:ref:`dict` or :ref:`timeseries`), and do not alter the data in the +filtered collection. They return a new collection of the same type, with its content filtered or altered. + +The syntax is as follows: + +.. code:: aql + + collection | filterName(argument1, argument2) + +The number of arguments varies depending on the filter, and filters can be chained. Some filters, like +:ref:`fields` or :ref:`setFields` for example, take a variable number of arguments. + +.. code:: aql + + collection | filter1(argument1) | filter2(argument1, argument2) | filter3(argument1) + +Some filters, such as :ref:`map` or :ref:`where` take expressions as arguments, and set metavariables that can +be used in these expressions to manipulate the content of the collection. Here are some examples +with a :ref:`dict` but these filters work with :ref:`timeseries` as well. For more examples with either type of +collection, see the detailed documentation for each filter. + +.. code:: aqlp + + >>> let d = newDict() | setFields("k1", 1, "k2", 2, "k3", 3) + >>> d + dict{ + k1: 1 + k2: 2 + k3: 3 + } + >>> d | map(_value * 10) + dict{ + k1: 10 + k2: 20 + k3: 30 + } + >>> d + dict{ + k1: 1 + k2: 2 + k3: 3 + } + >>> d | map(_value * 10) | where(_value <= 20) + dict{ + k1: 10 + k2: 20 + } + >>> d | map(str(_value * 10) + _key) + dict{ + k1: 10k1 + k2: 20k2 + k3: 30k3 + } + +The expression within a filter can use nested filters. + +.. code:: aqlp + + >>> let d = newDict() | setFields("d1", newDict() | setFields("k1", 1), "d2", newDict() | setFields("k1", 2)) + dict{ + d1: dict{ + k1: 1 + } + d2: dict{ + k1: 2 + } + } + >>> d | map(_value * 10) + error: input:1:4: input:1:15: operator * cannot be used with dict and num + >>> d | map(_value | map(_value * 10)) + dict{ + d1: dict{ + k1: 10 + } + d2: dict{ + k1: 20 + } + } + +Directives +---------- + +Directives allow setting some options before running an AQL script. They must be specified at the beginning +of the script code and will be set for the entire execution. + +The syntax is as follows: + +.. code:: aql + + %directiveName = true|false + +The only directive currently allowed is ``includeDecommissionedDevices``. It makes ``device`` dataset +wildcards include decommissioned devices' datasets. By default, these datasets are not included. + +.. code:: aql + + %includeDecommissionedDevices = true + + `*:/Sysdb/some/path/to/data` + + +.. _namedwildcards: + +Named wildcards and per-value execution +--------------------------------------- + +.. warning:: + + This section covers features that apply outside of the scope of a single AQL script execution. + Named wildcards are a part of AQL syntax but will modify how the interpreter behaves, by making + it run the script multiple times instead of one, with different input variables at each execution. + + This execution can then produce multiple outputs. + +Default behaviour +^^^^^^^^^^^^^^^^^ + +It is possible to insert a named wildcard into a query with the following syntax: ````. +When a named wildcard is set in a query, the interpreter catches it before running the AQL script; +instead of running it once, it runs the script multiple times for each path element matching the named +wildcard. + +In each run, the value of the path element is also set by the interpreter as a metavariable. +The name of that variable is always prefixed with an underscore, like all metavariables, but you do not +have to specify that underscore in the wildcard name. Therefore, these two syntaxes have the exact same +result, with the path element being stored in variable ``_device`` + +.. code:: aql + + `analytics:/Devices//interfaces/data` + +.. code:: aql + + `analytics:/Devices/<_device>/interfaces/data` + +Example: + +Regular wildcard: + +.. code:: aql + + let data = `*:/some/path/to/data`[1m] + data # This contains the data for all datasets (type = dict of timeseries) + +Named wildcard: + +.. code:: aql + + let data = `:/some/path/to/data`[1m] + let deviceName = _d # deviceName is a single string value, the name of the dataset in the current run + data # This contains only the data for one dataset (type = timeseries) + +For the named wildcard, the AQL script runs multiple times and the interpreter returns the list of all the +outputs. The user only has to manage data for one single dataset within the AQL code. + +For the regular wildcard, the AQL script runs only once, and contains the data of a datasets in a dict. +The user has to deal with the data of all the datasets manually. + + +Manual user input +^^^^^^^^^^^^^^^^^ + +When running AQL scripts through CLI, the Service API, or directly using the AQL interpreter library, +it is possible to pass input variables to manually control the multiple runs of named wildcards from +outside the scope of the AQL script. + +In the AQL library, this is handled through the ``inputVars`` parameter, in the Service API, through the +``varsets`` field. + +In any case, the field is a list that defines the list of times the query will be run. +Each element of the list is a list or map of the variables that the interpreter will set in the environment +before running the AQL script. + +If these varsets contain values that match the variable name of a named wildcard, the interpreter will +not perform the global GET on this named wildcard and instead run the query one or several times, following +the runs defined in the varset. + +Example: + +With these input variable sets: + +.. code:: json + + [ + {"_d": "JPE123456", "_i": "Ethernet5"}, + {"_d": "JPE123456", "_i": "Ethernet6"}, + {"_d": "JPE654321", "_i": "Ethernet1"} + ] + +The following query will just run three times, twice for device ``JPE123456`` (with interface ``Ethernet5`` +the first time and ``Ethernet6`` the second), and once for device ``JPE654321``, with interface ``Ethernet1``. + +.. code:: aql + + let interfaceData = `:/Sysdb/hardware/archer/xcvr/status/all/`[10m] + let deviceAndInterfaceNames = _d + " " + _i # This is just a string containing the device and interface name + interfaceData # This is a timeseries containing the last 10 mins of data for the current intf and device \ No newline at end of file diff --git a/_sources/examples/interface_states/admin_state.rst.txt b/_sources/examples/interface_states/admin_state.rst.txt new file mode 100644 index 0000000..f6ee2a7 --- /dev/null +++ b/_sources/examples/interface_states/admin_state.rst.txt @@ -0,0 +1,11 @@ +Admin state of interfaces per device +------------------------------------ + +.. literalinclude:: admin_state.aql + :language: aql + +.. image:: admin_state.png + :width: 600 + :alt: Admin state of interfaces per device + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/interface_states/count_if_nz_out_or_in_trfk.rst.txt b/_sources/examples/interface_states/count_if_nz_out_or_in_trfk.rst.txt new file mode 100644 index 0000000..306d93b --- /dev/null +++ b/_sources/examples/interface_states/count_if_nz_out_or_in_trfk.rst.txt @@ -0,0 +1,11 @@ +Count interfaces with non-zero outbound OR inbound traffic (with key existence check) +------------------------------------------------------------------------------------- + +.. literalinclude:: count_if_nz_out_or_in_trfk.aql + :language: aql + +.. image:: count_if_nz_out_or_in_trfk.png + :width: 600 + :alt: Count interfaces with non-zero outbound OR inbound traffic (with key existence check) + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/interface_states/count_if_nz_out_trfk.rst.txt b/_sources/examples/interface_states/count_if_nz_out_trfk.rst.txt new file mode 100644 index 0000000..f87a912 --- /dev/null +++ b/_sources/examples/interface_states/count_if_nz_out_trfk.rst.txt @@ -0,0 +1,11 @@ +Count interfaces with non-zero outbound traffic +----------------------------------------------- + +.. literalinclude:: count_if_nz_out_trfk.aql + :language: aql + +.. image:: count_if_nz_out_trfk.png + :width: 600 + :alt: Count interfaces with non-zero outbound traffic + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/interface_states/if_info_filter_port_desc.rst.txt b/_sources/examples/interface_states/if_info_filter_port_desc.rst.txt new file mode 100644 index 0000000..5e0a30f --- /dev/null +++ b/_sources/examples/interface_states/if_info_filter_port_desc.rst.txt @@ -0,0 +1,11 @@ +Interface Information wtih a filter on Port Description +------------------------------------------------------- + +.. literalinclude:: if_info_filter_port_desc.aql + :language: aql + +.. image:: if_info_filter_port_desc.png + :width: 600 + :alt: Port Utilization + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/interface_states/index.rst.txt b/_sources/examples/interface_states/index.rst.txt new file mode 100644 index 0000000..a777814 --- /dev/null +++ b/_sources/examples/interface_states/index.rst.txt @@ -0,0 +1,16 @@ +Interface States Examples +========================= + +.. include:: admin_state.rst + +.. include:: count_if_nz_out_trfk.rst + +.. include:: count_if_nz_out_or_in_trfk.rst + +.. include:: if_info_filter_port_desc.rst + +.. include:: lanz_queue_size.rst + +.. include:: port_utilization.rst + +.. include:: intf_counter_rate_sum_per_dev.rst diff --git a/_sources/examples/interface_states/intf_counter_rate_sum_per_dev.rst.txt b/_sources/examples/interface_states/intf_counter_rate_sum_per_dev.rst.txt new file mode 100644 index 0000000..cfa6b59 --- /dev/null +++ b/_sources/examples/interface_states/intf_counter_rate_sum_per_dev.rst.txt @@ -0,0 +1,14 @@ +Interface counters sum per interface list per device +----------------------------------------------------- + +.. note:: + Note that `aggregate()` function requires AQL revision 4+ to work. + +.. literalinclude:: intf_counter_rate_sum_per_dev.aql + :language: aql + +.. image:: intf_counter_rate_sum_per_dev.png + :width: 600 + :alt: Interface counters per interface list per device + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/interface_states/lanz_queue_size.rst.txt b/_sources/examples/interface_states/lanz_queue_size.rst.txt new file mode 100644 index 0000000..6c5245c --- /dev/null +++ b/_sources/examples/interface_states/lanz_queue_size.rst.txt @@ -0,0 +1,11 @@ +LANZ queue Size information filtering the null-values +----------------------------------------------------- + +.. literalinclude:: lanz_queue_size.aql + :language: aql + +.. image:: lanz_txlatency.png + :width: 600 + :alt: LANZ queue Size information filtering the null-values + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/interface_states/port_utilization.rst.txt b/_sources/examples/interface_states/port_utilization.rst.txt new file mode 100644 index 0000000..a869824 --- /dev/null +++ b/_sources/examples/interface_states/port_utilization.rst.txt @@ -0,0 +1,11 @@ +Port Utilization +---------------- + +.. literalinclude:: port_utilization.aql + :language: aql + +.. image:: port_utilization.png + :width: 600 + :alt: Port Utilization + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/layer2_and_layer3/arp_entries.rst.txt b/_sources/examples/layer2_and_layer3/arp_entries.rst.txt new file mode 100644 index 0000000..fa90551 --- /dev/null +++ b/_sources/examples/layer2_and_layer3/arp_entries.rst.txt @@ -0,0 +1,11 @@ +Number of ARP entries across all devices +---------------------------------------- + +.. literalinclude:: arp_entries.aql + :language: aql + +.. image:: arp_entries.png + :width: 600 + :alt: Number of ARP entries across all devices + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/layer2_and_layer3/bgp_states.rst.txt b/_sources/examples/layer2_and_layer3/bgp_states.rst.txt new file mode 100644 index 0000000..573419a --- /dev/null +++ b/_sources/examples/layer2_and_layer3/bgp_states.rst.txt @@ -0,0 +1,26 @@ +BGP States +---------- + +BGP Session Status +^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: bgp_states1.aql + :language: aql + +BGP Session Details in the Default VRF for all Devices +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: bgp_states2.aql + :language: aql + +BGP Sessions that are Not Established +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: bgp_states3.aql + :language: aql + +.. image:: bgp_states.png + :width: 600 + :alt: BGP Sessions that are Not Established + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/layer2_and_layer3/capacity_planning.rst.txt b/_sources/examples/layer2_and_layer3/capacity_planning.rst.txt new file mode 100644 index 0000000..ea4273b --- /dev/null +++ b/_sources/examples/layer2_and_layer3/capacity_planning.rst.txt @@ -0,0 +1,22 @@ +Capacity Planning Routing and Switching +--------------------------------------- + +High Density Leaf Switches Numbers (7050X3) Tagged with HDL Label +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: capacity_planning_rs1.aql + :language: aql + +Low Density Leaf Switches Numbers (7020R) Tagged with the LDL Label +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: capacity_planning_rs2.aql + :language: aql + +EVPN Gateways Numbers (7280R2) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: capacity_planning_evpn_gw_numbers.aql + :language: aql + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/layer2_and_layer3/igmp_snooping.rst.txt b/_sources/examples/layer2_and_layer3/igmp_snooping.rst.txt new file mode 100644 index 0000000..8b871c7 --- /dev/null +++ b/_sources/examples/layer2_and_layer3/igmp_snooping.rst.txt @@ -0,0 +1,17 @@ +IGMP Snooping Table +------------------- + +.. note:: + IGMP Snooping states are not streamed by default and the "/Sysdb/bridging/igmpsnooping" needs to be added to the TerminAttr include list using the tastreaming.TerminattrStreaming service API. + Examples can be found on `Examples <../../index_examples.html#terminattrstreamingpermitlistnote>`_ + + +.. literalinclude:: igmp_snooping.aql + :language: aql + +.. image:: igmp_snooping.png + :width: 600 + :alt: IGMP Snooping Table + + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/layer2_and_layer3/index.rst.txt b/_sources/examples/layer2_and_layer3/index.rst.txt new file mode 100644 index 0000000..4e71e43 --- /dev/null +++ b/_sources/examples/layer2_and_layer3/index.rst.txt @@ -0,0 +1,18 @@ +Layer 2 and Layer 3 examples +========================= + +.. include:: arp_entries.rst + +.. include:: bgp_states.rst + +.. include:: webinar03_bgp.rst + +.. include:: capacity_planning.rst + +.. include:: igmp_snooping.rst + +.. include:: mac_entries.rst + +.. include:: macs_per_device.rst + +.. include:: vrfs_in_vlans.rst diff --git a/_sources/examples/layer2_and_layer3/mac_entries.rst.txt b/_sources/examples/layer2_and_layer3/mac_entries.rst.txt new file mode 100644 index 0000000..35df94c --- /dev/null +++ b/_sources/examples/layer2_and_layer3/mac_entries.rst.txt @@ -0,0 +1,11 @@ +Number of MAC addresses across all devices +------------------------------------------ + +.. literalinclude:: mac_entries.aql + :language: aql + +.. image:: mac_entries.png + :width: 600 + :alt: Number of MAC addresses across all devices + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/layer2_and_layer3/macs_per_device.rst.txt b/_sources/examples/layer2_and_layer3/macs_per_device.rst.txt new file mode 100644 index 0000000..7b2943e --- /dev/null +++ b/_sources/examples/layer2_and_layer3/macs_per_device.rst.txt @@ -0,0 +1,11 @@ +Number of MACs per device per interface +--------------------------------------- + +.. literalinclude:: macs_per_device.aql + :language: aql + +.. image:: macs_per_device.png + :width: 600 + :alt: Number of MACs per device per interface + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/layer2_and_layer3/vrfs_in_vlans.rst.txt b/_sources/examples/layer2_and_layer3/vrfs_in_vlans.rst.txt new file mode 100644 index 0000000..d7d0c99 --- /dev/null +++ b/_sources/examples/layer2_and_layer3/vrfs_in_vlans.rst.txt @@ -0,0 +1,14 @@ +List of Configured VLANs per VRFs +--------------------------------------- + +.. literalinclude:: vrfs_in_vlans.aql + :language: aql + +.. literalinclude:: vrfs_in_vlans_all.aql + :language: aql + +.. image:: vrfs_in_vlans.png + :width: 600 + :alt: List of Configured VLANs per VRF + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/layer2_and_layer3/webinar03_bgp.rst.txt b/_sources/examples/layer2_and_layer3/webinar03_bgp.rst.txt new file mode 100644 index 0000000..b3f3486 --- /dev/null +++ b/_sources/examples/layer2_and_layer3/webinar03_bgp.rst.txt @@ -0,0 +1,40 @@ +TAC Webinar03 2023 - BGP States +------------------------------- + +BGP Summary +^^^^^^^^^^^ + +.. literalinclude:: webinar03_bgp_states_bgp_sum.aql + :language: aql + +.. image:: webinar03_bgp1.png + :width: 600 + :alt: IBGP Summary + +BGP Sessions Flaps +^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: webinar03_bgp_states_bgp_flaps.aql + :language: aql + +BGP Historical state tracker +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: webinar03_bgp_states_hist_tracker.aql + :language: aql + +.. image:: webinar03_bgp2.png + :width: 600 + :alt: BGP Flaps and Historical state tracker + +BGP Syslog Messages +^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: webinar03_bgp_states_bgp_syslogs.aql + :language: aql + +.. image:: webinar03_bgp3.png + :width: 600 + :alt: BGP Syslog Messages + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/system_health/abootfn44.rst.txt b/_sources/examples/system_health/abootfn44.rst.txt new file mode 100644 index 0000000..3b8e13f --- /dev/null +++ b/_sources/examples/system_health/abootfn44.rst.txt @@ -0,0 +1,11 @@ +Listing of devices affected by FN44 +----------------------------------- + +.. literalinclude:: abootfn44.aql + :language: aql + +.. image:: abootfn44.png + :width: 600 + :alt: Listing of devices affected by FN44 + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/system_health/eol_planning.rst.txt b/_sources/examples/system_health/eol_planning.rst.txt new file mode 100644 index 0000000..9fd00e0 --- /dev/null +++ b/_sources/examples/system_health/eol_planning.rst.txt @@ -0,0 +1,11 @@ +EoL Planning +------------ + +.. literalinclude:: eol_planning.aql + :language: aql + +.. image:: eol_planning.png + :width: 600 + :alt: EoL Planning + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/system_health/fn72.rst.txt b/_sources/examples/system_health/fn72.rst.txt new file mode 100644 index 0000000..5c1c874 --- /dev/null +++ b/_sources/examples/system_health/fn72.rst.txt @@ -0,0 +1,11 @@ +Listing of devices affected by FN72 +----------------------------------- + +.. literalinclude:: fn72.aql + :language: aql + +.. image:: fn72.png + :width: 600 + :alt: Listing of devices affected by FN72 + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/system_health/hardware_health_check.rst.txt b/_sources/examples/system_health/hardware_health_check.rst.txt new file mode 100644 index 0000000..ce62dbd --- /dev/null +++ b/_sources/examples/system_health/hardware_health_check.rst.txt @@ -0,0 +1,26 @@ +Hardware Health Check +--------------------- + +Power Supply Status per Device +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: hardware_health_check_psu.aql + :language: aql + +Fan Status per Device +^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: hardware_health_check_fan.aql + :language: aql + +Temperature Sensors per Device +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: hardware_health_check_temp_sensor.aql + :language: aql + +.. image:: hardware_health_check.png + :width: 600 + :alt: Hardware Health Check + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/system_health/important_pod_count.rst.txt b/_sources/examples/system_health/important_pod_count.rst.txt new file mode 100644 index 0000000..b179bd8 --- /dev/null +++ b/_sources/examples/system_health/important_pod_count.rst.txt @@ -0,0 +1,32 @@ +Important Pod Count +------------------- + +Number of Leaf Switches +^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: important_pod_count_leafs.aql + :language: aql + +Number of VTEPs +^^^^^^^^^^^^^^^ + +.. literalinclude:: important_pod_count_vteps.aql + :language: aql + +Total VLAN Count +^^^^^^^^^^^^^^^^ + +.. literalinclude:: important_pod_count_vlans.aql + :language: aql + +Max Size of the Floodlist +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: important_pod_count_max_size_floodlist.aql + :language: aql + +.. image:: important_pod_count.png + :width: 600 + :alt: ImportantPodCount + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/system_health/index.rst.txt b/_sources/examples/system_health/index.rst.txt new file mode 100644 index 0000000..6b6ceda --- /dev/null +++ b/_sources/examples/system_health/index.rst.txt @@ -0,0 +1,28 @@ +System Health Examples +========================= + +.. include:: abootfn44.rst + +.. include:: eol_planning.rst + +.. include:: fn72.rst + +.. include:: hardware_health_check.rst + +.. include:: important_pod_count.rst + +.. include:: index.rst + +.. include:: ntp_stats.rst + +.. include:: output_power_over_48h.rst + +.. include:: system_health_check.rst + +.. include:: tcam_capacity.rst + +.. include:: varcore.rst + +.. include:: xcvr_sn_list.rst + +.. include:: xcvr_sn_list_filtered.rst diff --git a/_sources/examples/system_health/ntp_stats.rst.txt b/_sources/examples/system_health/ntp_stats.rst.txt new file mode 100644 index 0000000..e7e39a6 --- /dev/null +++ b/_sources/examples/system_health/ntp_stats.rst.txt @@ -0,0 +1,11 @@ +NTP Stats +--------- + +.. literalinclude:: ntp_stats.aql + :language: aql + +.. image:: ntp_stats.png + :width: 600 + :alt: NTP Stats + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/system_health/output_power_over_48h.rst.txt b/_sources/examples/system_health/output_power_over_48h.rst.txt new file mode 100644 index 0000000..e6d3a75 --- /dev/null +++ b/_sources/examples/system_health/output_power_over_48h.rst.txt @@ -0,0 +1,11 @@ +PowerSupply Output +------------------ + +.. literalinclude:: output_power_over_48h.aql + :language: aql + +.. image:: output_power_over_48h.png + :width: 600 + :alt: PowerSupply Output + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/system_health/system_health_check.rst.txt b/_sources/examples/system_health/system_health_check.rst.txt new file mode 100644 index 0000000..2a7d3a9 --- /dev/null +++ b/_sources/examples/system_health/system_health_check.rst.txt @@ -0,0 +1,30 @@ +System Health Check +------------------- + +CPU Utilization in the Fabric +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: system_health_check_cpu_fabric.aql + :language: aql + +Memory Usage in the Fabric +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: system_health_check_ram_fabric.aql + :language: aql + +/mnt/flash usage +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: system_health_check_flash_usage.aql + :language: aql + +.. image:: system_health_check1.png + :width: 600 + :alt: system_health_check1 + +.. image:: system_health_check2.png + :width: 600 + :alt: system_health_check2 + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/system_health/tcam_capacity.rst.txt b/_sources/examples/system_health/tcam_capacity.rst.txt new file mode 100644 index 0000000..15b3569 --- /dev/null +++ b/_sources/examples/system_health/tcam_capacity.rst.txt @@ -0,0 +1,26 @@ +TCAM Capacity +------------- + +7280R2 Switches +^^^^^^^^^^^^^^^ + +.. literalinclude:: tcam_7280r2.aql + :language: aql + +7050X3 Switches +^^^^^^^^^^^^^^^ + +.. literalinclude:: tcam_7050x3.aql + :language: aql + +7020R Switches +^^^^^^^^^^^^^^ + +.. literalinclude:: tcam_7020r.aql + :language: aql + +.. image:: tcam_capacity.png + :width: 600 + :alt: TCAM Capacity + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/system_health/varcore.rst.txt b/_sources/examples/system_health/varcore.rst.txt new file mode 100644 index 0000000..b077fc2 --- /dev/null +++ b/_sources/examples/system_health/varcore.rst.txt @@ -0,0 +1,13 @@ +Listing Devices that have a file in /var/core +--------------------------------------------- + +> Useful to understand if there were any agent crashes on EOS + +.. literalinclude:: varcore.aql + :language: aql + +.. image:: varcore.png + :width: 600 + :alt: varcore files + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/system_health/xcvr_sn_list.rst.txt b/_sources/examples/system_health/xcvr_sn_list.rst.txt new file mode 100644 index 0000000..1860047 --- /dev/null +++ b/_sources/examples/system_health/xcvr_sn_list.rst.txt @@ -0,0 +1,11 @@ +List the serial numbers for all transceivers +-------------------------------------------- + +.. literalinclude:: xcvr_sn_list.aql + :language: aql + +.. image:: xcvr_sn_list.png + :width: 600 + :alt: List the serial numbers for all transceivers + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/examples/system_health/xcvr_sn_list_filtered.rst.txt b/_sources/examples/system_health/xcvr_sn_list_filtered.rst.txt new file mode 100644 index 0000000..c5a2e40 --- /dev/null +++ b/_sources/examples/system_health/xcvr_sn_list_filtered.rst.txt @@ -0,0 +1,11 @@ +List the transceiver serial numbers that match the input regex +-------------------------------------------------------------- + +.. literalinclude:: xcvr_sn_list_filtered.aql + :language: aql + +.. image:: xcvr_sn_list_filtered.png + :width: 600 + :alt: List the transceiver serial numbers that match the input regex + +:download:`Download the Dashboard JSON here ` diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 0000000..86a5c54 --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,24 @@ +AQL Documentation +================= + +This page is the official documentation for the CloudVision Advanced Query Language (AQL). + +The AQL language can be used with Arista's CloudVision to configure dashboards, query data, configure custom event rules, etc. + +.. warning:: + + AQL scripts and dashboards may stop working and require a manual update following a hardware upgrade or EOS image update, as data paths may have changed. + + +.. index_quicklearn is for internal use only but sphinx will still be able to build the website without it. +.. It will only be added by jenkins to build the internal documentation + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + index_quicklearn + index_doc + index_stdlib + changelog + index_examples diff --git a/_sources/index_doc.rst.txt b/_sources/index_doc.rst.txt new file mode 100644 index 0000000..256f537 --- /dev/null +++ b/_sources/index_doc.rst.txt @@ -0,0 +1,12 @@ +.. When a file is missing, sphinx still builds the website without that content. +.. The internal information will therefore not be published on github because it will be missing. + +.. include:: doc/internal.rst + +Language Specification +====================== + +.. contents:: Table of Contents + :depth: 3 + +.. include:: doc/language_reference.rst \ No newline at end of file diff --git a/_sources/index_examples.rst.txt b/_sources/index_examples.rst.txt new file mode 100644 index 0000000..684f7da --- /dev/null +++ b/_sources/index_examples.rst.txt @@ -0,0 +1,49 @@ +AQL Examples +============ + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + examples/interface_states/index + examples/layer2_and_layer3/index + examples/system_health/index + +.. _terminattrStreamingPermitlistNote: + +.. note:: + Some telemetry states are not streamed by default and they would need to be added to the TerminAttr include list using the tastreaming.TerminattrStreaming service API, e.g.: + + On-prem example: + + :: + + curl -sS -kX POST --header 'Accept: application/json' -b access_token=`cat token` \ + 'https://192.0.2.1/api/v3/services/tastreaming.TerminattrStreaming/SubscribeTAPaths' \ + + -d '{ "filter": { "app_name": "app1", "include_paths": [ "/Sysdb/bridging/igmpsnooping" ]}}' + + CVaaS example: + + :: + + curl -sS -kX POST --header 'Accept: application/json' -b access_token=`cat token` \ + 'https://www.arista.io/api/v3/services/tastreaming.TerminattrStreaming/SubscribeTAPaths' \ + + -d '{ "filter": { "app_name": "app1", "include_paths": [ "/Sysdb/bridging/igmpsnooping" ]}}' + + Result: + + :: + + [{}] + + For CVaaS please make sure to use the correct regional URL: + + - United States 1a: **www.cv-prod-us-central1-a.arista.io** + - United States 1c: **www.cv-prod-us-central1-c.arista.io** + - Japan: **www.cv-prod-apnortheast-1.arista.io** + - Germany: **www.cv-prod-euwest-2.arista.io** + - Australia: **www.cv-prod-ausoutheast-1.arista.io** + - Canada: **www.cv-prod-na-northeast1-b.arista.io** + - United Kingdom: **www.cv-prod-uk-1.arista.io** \ No newline at end of file diff --git a/_sources/index_stdlib.rst.txt b/_sources/index_stdlib.rst.txt new file mode 100644 index 0000000..f3287db --- /dev/null +++ b/_sources/index_stdlib.rst.txt @@ -0,0 +1,16 @@ +AQL Standard Library +==================== + +.. contents:: Table of Contents + :depth: 4 + +Functions +--------- + +.. include:: doc/functions.rst + +Filters +------- + +.. include:: doc/filters.rst + diff --git a/_static/_sphinx_javascript_frameworks_compat.js b/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 0000000..8141580 --- /dev/null +++ b/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 0000000..f316efc --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css new file mode 100644 index 0000000..c718cee --- /dev/null +++ b/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff b/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff2 b/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff b/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff2 b/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/_static/css/fonts/fontawesome-webfont.eot b/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/_static/css/fonts/fontawesome-webfont.svg b/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/css/fonts/fontawesome-webfont.ttf b/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/_static/css/fonts/fontawesome-webfont.woff b/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/_static/css/fonts/fontawesome-webfont.woff2 b/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/_static/css/fonts/lato-bold-italic.woff b/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff differ diff --git a/_static/css/fonts/lato-bold-italic.woff2 b/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/_static/css/fonts/lato-bold.woff b/_static/css/fonts/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff differ diff --git a/_static/css/fonts/lato-bold.woff2 b/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff2 differ diff --git a/_static/css/fonts/lato-normal-italic.woff b/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff differ diff --git a/_static/css/fonts/lato-normal-italic.woff2 b/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/_static/css/fonts/lato-normal.woff b/_static/css/fonts/lato-normal.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/_static/css/fonts/lato-normal.woff differ diff --git a/_static/css/fonts/lato-normal.woff2 b/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/_static/css/fonts/lato-normal.woff2 differ diff --git a/_static/css/theme.css b/_static/css/theme.css new file mode 100644 index 0000000..19a446a --- /dev/null +++ b/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/_static/custom.css b/_static/custom.css new file mode 100644 index 0000000..671c80c --- /dev/null +++ b/_static/custom.css @@ -0,0 +1,121 @@ +.wy-nav-content { + max-width: 100%; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, + 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', + Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + font-size: 16px; +} + +.wy-side-nav-search > a { + font-size: 30px; +} + +.wy-side-nav-search > .icon::before { + display: none; +} + +h2, h3, h4, h5, h6 { + font-family: "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; + text-rendering: optimizeLegibility; + line-height: 110% !important; + margin: 2.5rem 0 1.5rem 0; +} + +h1 { + font-family: "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; + text-align: center; + text-transform: uppercase; + color: #222; + font-size: 3.25rem; + text-rendering: optimizeLegibility; + font-weight: 100; +} + +h2 { + font-size: 2.5rem; + margin-top: 4rem; +} + +h3 { + font-size: 2rem; +} + +h4 { + font-size: 1.7rem; +} + +h5 { + font-size: 1.3rem; +} + +h6 { + font-size: 1.2rem; +} + +pre, code { + font-size: 15px !important; + font-family: "Consolas", menlo, monospace; +} + +.rst-footer-buttons { + margin-top: 2.5rem; +} + +.highlight .k { + color: #00a8c8; +} + +.highlight .s { + color: #991919; +} + +.highlight .m { + color: #008e0e +} + +.highlight .l { /* AQL query */ + color: #aa55ff; +} + +.highlight .o { /* operators */ + color: #f92672; +} + +.highlight .esc { + color: #d88200; +} + +a.reference.internal > .std-ref, .xref.std-ref { + color: #e74c3c; + white-space: normal; + box-sizing: border-box; + font-family: SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace; + border: 1px solid #e1e4e5; + padding: 2px 5px; + background-color: #fff; +} + +.math { + white-space: normal; + box-sizing: border-box; + border: 1px solid #e1e4e5; + padding: 2px 5px; + background-color: #fff; +} + +a.reference.internal > .std-ref::after { + content: '🔗'; + font-size: 11px; + padding-left: 2px; +} + +.sectnum { + font-size: 80%; +} + +h1 > .sectnum { + display: none; +} \ No newline at end of file diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 0000000..4d67807 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 0000000..35d6518 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '4', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/jquery.js b/_static/jquery.js new file mode 100644 index 0000000..c4c6022 --- /dev/null +++ b/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/_static/js/html5shiv.min.js b/_static/js/html5shiv.min.js new file mode 100644 index 0000000..cd1c674 --- /dev/null +++ b/_static/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/_static/js/theme.js b/_static/js/theme.js new file mode 100644 index 0000000..1fddb6e --- /dev/null +++ b/_static/js/theme.js @@ -0,0 +1 @@ +!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 0000000..d96755f Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 0000000..7107cec Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 0000000..84ab303 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #9C6500 } /* Comment.Preproc */ +.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #E40000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #008400 } /* Generic.Inserted */ +.highlight .go { color: #717171 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #687822 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #767600 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #666666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #0000FF } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 0000000..b08d58c --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,620 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + "Search finished, found ${resultCount} page(s) matching the search query." + ).replace('${resultCount}', resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; + + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + _parseQuery: (query) => { + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename]. + const normalResults = []; + const nonMainIndexResults = []; + + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase().trim(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score + boost, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } + } + } + } + + // lookup as object + objectTerms.forEach((term) => + normalResults.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 0000000..8a96c69 --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/changelog.html b/changelog.html new file mode 100644 index 0000000..936fdbe --- /dev/null +++ b/changelog.html @@ -0,0 +1,207 @@ + + + + + + + Language revisions / Change log — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Language revisions / Change log

+
+

Revision per CloudVision release

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

CVP release

AQL revision

CVaaS

4

2023.3.*

4

2023.2.*

4

2023.1.*

4

2022.3.*

3

2022.2.*

3

2022.1.*

3

2021.3.*

3

2021.2.*

2

2021.1.*

2

2020.3.*

1

2020.2.*

1

2020.1.*

1

+
+
+

Change log

+
+

Revision 4

+ +
+
+

Revision 3

+ +
+
+

Revision 2

+

No change to the AQL syntax or standard library.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/doc/filters.html b/doc/filters.html new file mode 100644 index 0000000..12175e4 --- /dev/null +++ b/doc/filters.html @@ -0,0 +1,904 @@ + + + + + + + field — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

field

+

Added in revision 1

+

Filter field applies to a timeseries of dicts. Returns a timeseries of the value at the +specified key for each entry of the timeseries that contains this field. Entries that don’t +contain the key are not in the output timeseries.

+
    +
  • The filtered value is a timeseries of dicts

  • +
  • The first and only parameter is the key to keep in the dicts

  • +
+
>>> `analytics:/path/to/data`[1]
+timeseries{
+    tstamp1: dict{key1: val1, key2: val2, key3: val3}
+    tstamp2: dict{key1: val4, key2: val5}
+}
+>>> _ | field("key1")
+timeseries{
+    tstamp1: val1
+    tstamp2: val4
+}
+
+
+
+
+

fields

+

Added in revision 3

+

Filter fields applies to a dict. It filters it and returns a new dict containing only the +key/value pairs whose key is passed as parameter.

+
    +
  • The filtered value is a dict

  • +
  • There is a variable (\(0\) to \(n\)) number of parameters: they are the keys to keep in the output dict

  • +
+
+

Note

+

If a specified key is missing from the source dict, the filter will not fail but the +output dict will also be missing that key

+
+
>>> let d = `analytics:/path/to/data/*` | map(merge(_value))
+>>> d
+dict{
+    key0: dict{key1: val1, key2: val2, key3: val3}
+    key01: dict{key1: val4, key2: val5}
+    key02: dict{key1: val6, key2: val7}
+}
+>>> d | fields("key0", "key01")
+dict{
+    key0: dict{key1: val1, key2: val2, key3: val3}
+    key01: dict{key1: val4, key2: val5}
+}
+>>> d | fields("k")
+dict{
+}
+>>> d | fields()
+dict{
+}
+>>> d | fields("key01") | map(_value | fields("key2"))
+dict{
+    key01: dict{key2: val5}
+}
+
+
+
+
+

setFields

+

Added in revision 3

+

Filter setFields sets some key/value pairs in a dict. If the key already existed in the +filtered dict, its value will be replaced with the new one in the output dict (like all filters, +setFields returns a filtered copy of the dict and does not alter the source). If the key did not +exist in the filtered dict, the key/value pair will just be added to the output dict.

+
    +
  • The filtered value is a dict

  • +
  • There is a variable (0 to n) even number of parameters: they correspond to the list of key/value +pairs

  • +
+
>>> let d = newDict() | setFields("k1", "v1", "k2", 2.3, "k3", 3)
+>>> d
+dict{
+    k1: v1
+    k2: 2.3
+    k3: 3
+}
+>>> d | setFields("k4", newDict() | setFields("k5", "v5"))
+dict{
+    k1: v1
+    k2: 2.3
+    k3: 3
+    k4: dict{
+        k5: v5
+    }
+}
+>>> d
+dict{
+    k1: v1
+    k2: 2.3
+    k3: 3
+} # the source dict is not altered
+
+
+
+
+

applyDeletes

+

Added in revision 4

+

Filter applyDeletes applies the deletes to a timeseries. This timeseries must be freshly returned by a query. +Most filters remove the deletes information from timeseries, so this should be called before any other filter or function.

+

If no argument is passed, applying a delete will remove all entries with that delete’s key that were updated prior to the delete +itself. This use-case is mostly appropriate when used with the result of a query that does not contain historical data (state-only). +With historical data, this would wipe deleted entries from ever having existed in the timeseries, instead of signaling the end +of the entry at the moment of deletion.

+

If an argument is passed, then the expression defines a value that will be written at the moment of the delete for that key. +This use-case is more appropriate with historical data because it will not remove entries, but instead create an entry that signals +the end of the value.

+
    +
  • The filtered value is a timeseries that still contains delete information.

  • +
  • The only and optional parameter is the expression. Its value can be of any type after evaluation.

  • +
+

Usable metavariables in the expression are:

+
+
    +
  • _key or _1: key matching the delete

  • +
  • _index: index of the delete in the timeseries

  • +
  • _updindex: index of the last update for this key in the timeseries

  • +
  • _time or _2: time of the delete in the timeseries

  • +
  • _updtime: time of the last update for this key in the timeseries

  • +
  • _value or _3: last value prior to the delete for the deleted key

  • +
  • _src or _4: reference to the timeseries being filtered

  • +
+
+
+Example
>>> let a = `analytics:/tags/BugAlerts/Query/gNMIEnabled`[5]
+>>> a
+timeseries{
+    start: 2021-03-17 02:48:58.205235103 +0100 CET
+    end: 2022-10-19 13:30:33.722908 +0200 CEST
+    2021-03-17 02:48:58.205235103 +0100 CET: dict{
+        JAS12200014: true
+        JAS16040045: true
+        JAS17250006: true
+        JAS17250010: true
+        JAS17510146: true
+        JPE14171444: true
+        JPE17191574: true
+        SSJ17371234: true
+    }
+    2021-05-12 17:32:58.269740014 +0200 CEST: dict{
+        HSH14075043: true
+        HSH14075051: true
+    }
+    2021-11-03 17:09:46.753872494 +0100 CET: dict{
+        HSH14280171: true
+        HSH14420467: true
+        JPE14250224: true
+        JPE14383408: true
+        SSJ17049015: true
+        SSJ17374660: true
+    }
+    2021-11-11 05:09:22.273451668 +0100 CET: dict{
+        JAS14170008: true
+        JAS14210057: true
+        JAS17070003: true
+        JAS18170075: true
+        JPE14120478: true
+        JPE19280519: true
+    }
+    2022-02-18 23:08:10.204460235 +0100 CET: dict{
+        2568DB4A33177968A78C4FD5A8232159: true
+        6323DA7D2B542B5D09630F87351BEA41: true
+        BAD032986065E8DC14CBB6472EC314A6: true
+        CD0EADBEEA126915EA78E0FB4DC776CA: true
+    }
+    2022-02-22 00:48:45.347884243 +0100 CET: dict{0123F2E4462997EB155B7C50EC148767: true}
+    2022-07-18 18:10:07.772750473 +0200 CEST: dict{JPE20244151: true}
+}
+>>> deletes(a)
+timeseries{
+    start: 2021-03-17 02:48:58.205235103 +0100 CET
+    end: 2022-10-19 13:30:33.722908 +0200 CEST
+    2021-11-23 11:09:21.716099165 +0100 CET: dict{
+        HSH14075043: <nil>
+        HSH14075051: <nil>
+        HSH14280171: <nil>
+        HSH14420467: <nil>
+        JAS14170008: <nil>
+        JAS14210057: <nil>
+        JAS16040045: <nil>
+        JAS17070003: <nil>
+        JAS17250006: <nil>
+        JAS17250010: <nil>
+        JAS17510146: <nil>
+        JAS18170075: <nil>
+        JPE14120478: <nil>
+        JPE14171444: <nil>
+        JPE14250224: <nil>
+        JPE14383408: <nil>
+        JPE17191574: <nil>
+        JPE19280519: <nil>
+        SSJ17049015: <nil>
+        SSJ17371234: <nil>
+        SSJ17374660: <nil>
+    }
+    2022-02-22 00:48:45.347884243 +0100 CET: dict{
+        2568DB4A33177968A78C4FD5A8232159: <nil>
+        6323DA7D2B542B5D09630F87351BEA41: <nil>
+        BAD032986065E8DC14CBB6472EC314A6: <nil>
+        CD0EADBEEA126915EA78E0FB4DC776CA: <nil>
+    }
+}
+>>> a | applyDeletes()
+timeseries{
+    start: 2021-03-17 02:48:58.205235103 +0100 CET
+    end: 2022-10-19 13:30:33.722908 +0200 CEST
+    2021-03-17 02:48:58.205235103 +0100 CET: dict{JAS12200014: true}
+    2022-02-22 00:48:45.347884243 +0100 CET: dict{0123F2E4462997EB155B7C50EC148767: true}
+    2022-07-18 18:10:07.772750473 +0200 CEST: dict{JPE20244151: true}
+}
+>>> a | applyDeletes(_key+" is deleted, its value was " + str(_value))
+timeseries{
+    start: 2021-03-17 02:48:58.205235103 +0100 CET
+    end: 2022-10-19 13:30:33.722908 +0200 CEST
+    2021-03-17 02:48:58.205235103 +0100 CET: dict{
+        JAS12200014: true
+        JAS16040045: true
+        JAS17250006: true
+        JAS17250010: true
+        JAS17510146: true
+        JPE14171444: true
+        JPE17191574: true
+        SSJ17371234: true
+    }
+    2021-05-12 17:32:58.269740014 +0200 CEST: dict{
+        HSH14075043: true
+        HSH14075051: true
+    }
+    2021-11-03 17:09:46.753872494 +0100 CET: dict{
+        HSH14280171: true
+        HSH14420467: true
+        JPE14250224: true
+        JPE14383408: true
+        SSJ17049015: true
+        SSJ17374660: true
+    }
+    2021-11-11 05:09:22.273451668 +0100 CET: dict{
+        JAS14170008: true
+        JAS14210057: true
+        JAS17070003: true
+        JAS18170075: true
+        JPE14120478: true
+        JPE19280519: true
+    }
+    2021-11-23 11:09:21.716099165 +0100 CET: dict{
+        HSH14075043: HSH14075043 is deleted, its value was true
+        HSH14075051: HSH14075051 is deleted, its value was true
+        HSH14280171: HSH14280171 is deleted, its value was true
+        HSH14420467: HSH14420467 is deleted, its value was true
+        JAS14170008: JAS14170008 is deleted, its value was true
+        JAS14210057: JAS14210057 is deleted, its value was true
+        JAS16040045: JAS16040045 is deleted, its value was true
+        JAS17070003: JAS17070003 is deleted, its value was true
+        JAS17250006: JAS17250006 is deleted, its value was true
+        JAS17250010: JAS17250010 is deleted, its value was true
+        JAS17510146: JAS17510146 is deleted, its value was true
+        JAS18170075: JAS18170075 is deleted, its value was true
+        JPE14120478: JPE14120478 is deleted, its value was true
+        JPE14171444: JPE14171444 is deleted, its value was true
+        JPE14250224: JPE14250224 is deleted, its value was true
+        JPE14383408: JPE14383408 is deleted, its value was true
+        JPE17191574: JPE17191574 is deleted, its value was true
+        JPE19280519: JPE19280519 is deleted, its value was true
+        SSJ17049015: SSJ17049015 is deleted, its value was true
+        SSJ17371234: SSJ17371234 is deleted, its value was true
+        SSJ17374660: SSJ17374660 is deleted, its value was true
+    }
+    2022-02-18 23:08:10.204460235 +0100 CET: dict{
+        2568DB4A33177968A78C4FD5A8232159: true
+        6323DA7D2B542B5D09630F87351BEA41: true
+        BAD032986065E8DC14CBB6472EC314A6: true
+        CD0EADBEEA126915EA78E0FB4DC776CA: true
+    }
+    2022-02-22 00:48:45.347884243 +0100 CET: dict{
+        2568DB4A33177968A78C4FD5A8232159: 2568DB4A33177968A78C4FD5A8232159 is deleted, its value was true
+        6323DA7D2B542B5D09630F87351BEA41: 6323DA7D2B542B5D09630F87351BEA41 is deleted, its value was true
+        BAD032986065E8DC14CBB6472EC314A6: BAD032986065E8DC14CBB6472EC314A6 is deleted, its value was true
+        CD0EADBEEA126915EA78E0FB4DC776CA: CD0EADBEEA126915EA78E0FB4DC776CA is deleted, its value was true
+    }
+    2022-02-22 00:48:45.347884243 +0100 CET: dict{0123F2E4462997EB155B7C50EC148767: true}
+    2022-07-18 18:10:07.772750473 +0200 CEST: dict{JPE20244151: true}
+}
+
+
+
+
+

renameFields

+

Added in revision 3

+

Filter renameFields renames some keys in a dict. The keys which are not specified in the +arguments will be kept in the output dict. Use the fields filter to remove them.

+
    +
  • The filtered value is a dict

  • +
  • There is a variable (\(0\) to \(n\)) even number of parameters: they correspond to the list of old-key/new-key pairs

  • +
+
+

Note

+

If a specified key is missing from the source dict, the filter will not fail and that pair +will just be ignored

+
+
>>> let d = `analytics:/path/to/data/*` | map(merge(_value))
+>>> d
+dict{
+    key0: dict{key1: val1, key2: val2, key3: val3}
+    key01: dict{key1: val4, key2: val5}
+}
+>>> d | renameFields("key0", "newkey0")
+dict{
+    newkey0: dict{key1: val1, key2: val2, key3: val3}
+    key01: dict{key1: val4, key2: val5}
+}
+>>> d | renameFields("key0", "newkey0", "key01", "newkey01")
+dict{
+    newkey0: dict{key1: val1, key2: val2, key3: val3}
+    newkey01: dict{key1: val4, key2: val5}
+}
+>>> d | fields("key01") | map(_value | renameFields("key2", "newkey2"))
+dict{
+    key01: dict{key1: val4, newkey2: val5}
+}
+
+
+
+
+

where

+

Added in revision 1

+

Filter where returns a filtered timeseries or dict containing exclusively the entries of the input where +the predicate passed as parameter is true.

+
    +
  • The first and only parameter is the predicate. It is an expression, the value of which must be a boolean after evaluation.

  • +
+

Usable metavariables in the predicate for timeseries are:

+
+
    +
  • _index or _1: index of the current element (starting at \(0\))

  • +
  • _time or _2: timestamp of the current element

  • +
  • _value or _3: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
+

Usable metavariables in the predicate for dicts are:

+
+
    +
  • _key or _1: key of the current element

  • +
  • _value or _2: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
+
>>> `analytics:/path/to/data`[3] | field("key1")
+timeseries{
+    tstamp1: 1
+    tstamp2: 2
+    tstamp3: 3
+    tstamp4: 4
+}
+>>> _ | where(_value >= 3)
+timeseries{
+    tstamp3: 3
+    tstamp4: 4
+}
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["k3"] = 1
+>>> d["k4"] = 1
+>>> d | where(strContains(_key, "key"))
+dict{
+    "key1": 13
+    "key2": 1
+}
+
+
+
+
+

map

+

Added in revision 1

+

Filter map returns a timeseries or dict containing the results of the expression passed as parameter applied to each entry of the filtered timeseries or dict.

+
    +
  • The first and only parameter is the expression. Its value can be of any type after evaluation.

  • +
+

Usable metavariables in the expression for timeseries are:

+
    +
  • _index or _1: index of the current element (starting at \(0\))

  • +
  • _time or _2: timestamp of the current element

  • +
  • _value or _3: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+

Usable metavariables in the expression for dicts are:

+
    +
  • _key or _1: key of the current element

  • +
  • _value or _2: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
>>> `analytics:/path/to/data`[3]
+timeseries{
+    tstamp1: dict{key1: 1, key2: 12, key3: 11}
+    tstamp2: dict{key1: 2, key2: 123}
+    tstamp3: dict{key1: 3, key2: 78, key3: 42}
+    tstamp4: dict{key1: 4, key2: 68}
+}
+>>> _ | map(_value["key1"] + 1)
+timeseries{
+    tstamp1: 2
+    tstamp2: 3
+    tstamp3: 4
+    tstamp4: 5
+}
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["k3"] = 1
+>>> d["k4"] = 1
+>>> d | map(_key + "l")
+dict{
+    "key1": key1l
+    "key2": key2l
+    "k3": k3l
+    "k4": k4l
+}
+>>> d | map(_value^2)
+dict{
+    "key1": 169
+    "key2": 1
+    "k3": 1
+    "k4": 1
+}
+
+
+
+
+

mapne

+

Added in revision 1

+

Filter mapne (map-not-empty) returns a timeseries or dict containing the results of the +expression passed as first parameter applied to the result of the expression passed as second parameter +if its result is not empty. This applies to each entry of the filtered timeseries or dict.

+
    +
  • The first parameter is the main expression. Its value can be of any type after evaluation.

    +

    Usable metavariables in the expression for timeseries are:

    +
    +
      +
    • _index or _1: index of the current element (starting at \(0\))

    • +
    • _time or _2: timestamp of the current element

    • +
    • _value or _3: result of the second expression applied to the current element

    • +
    • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

    • +
    +
    +

    Usable metavariables in the expression for :ref:`dict`s are:

    +
    +
      +
    • _key or _1: key of the current element

    • +
    • _value or _2: result of the second expression applied to the current element

    • +
    • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

    • +
    +
    +
  • +
  • the second parameter is the filtering expression. Its value can be of any type after evaluation.

    +

    Usable metavariables in the expression for timeseries are:

    +
    +
      +
    • _index or _1: index of the current element (starting at 0)

    • +
    • _time or _2: timestamp of the current element

    • +
    • _value or _3: value of the current element

    • +
    • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

    • +
    +
    +

    Usable metavariables in the expression for dicts are:

    +
    +
      +
    • _key or _1: key of the current element

    • +
    • _value or _3 value of the current element

    • +
    • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

    • +
    +
    +
  • +
+
>>> `analytics:/path/to/*/data/with/wildcard`[3]
+dict {
+    pathElement1: timeseries{t1:1, t2:2, t3:3, t4:4}
+    pathElement2: timeseries{t5:5, t6:6, t7:7, t8:8}
+    pathElement3: timeseries{}
+    pathElement4: timeseries{t9:9, t10:10, t11:11, t12:12}
+}
+>>> _ | map(mean(_value))
+error: cannot compute mean of empty timeseries
+>>> _ | mapne(mean(_value), _value)
+dict {
+    pathElement1: 2.5
+    pathElement2: 6.5
+    pathElement4: 10.5
+}
+>>> `analytics:/path/to/data`[3]
+timeseries{
+    tstamp1: dict{k1:1, k2:2, k3:3, k4:4}
+    tstamp2: dict{k1:1, k2:2, k3:3, k4:4}
+    tstamp3: dict{}
+    tstamp4: dict{k1:1, k2:2, k3:3, k4:4}
+}
+>>> _ | map(mean(_value))
+error: cannot compute mean of empty dict
+>>> _ | mapne(mean(_value) + 12, _value)
+timeseries{
+    tstamp1: 14.5
+    tstamp2: 14.5
+    tstamp4: 14.5
+}
+
+
+
+
+

recmap

+

Added in revision 1

+

Filter recmap returns a timeseries or dict containing the results of the expression passed as parameter applied to each +entry of the filtered timeseries or dict, at the specified depth.

+
    +
  • The first parameter is the recursion depth (num).

  • +
  • The second parameter is the expression. Its value can be of any type after evaluation.

  • +
+

Usable metavariables in the expression for timeseries are:

+
    +
  • _index or _1: index of the current element (starting at \(0\))

  • +
  • _time or _2: timestamp of the current element

  • +
  • _value or _3: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+

Usable metavariables in the expression for dicts are:

+
    +
  • _key or _1: key of the current element

  • +
  • _value or _2: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
>>> `analytics:/path/to/*/data/with/*/2/wildcards`
+dict {
+    pe1: dict{pe1.1: timeseries{1, 2, 3}, pe1.2: timeseries{1, 2, 3}}
+    pe2: dict{pe2.1: timeseries{1, 2, 3}, pe2.2: timeseries{1, 2, 3}}
+    pe3: dict{pe3.1: timeseries{1, 2, 3}, pe3.2: timeseries{1, 2, 3}}
+} # we want the same recursion depth for every branch here, and stop at the timeseries level
+>>> let data = _
+>>> data | map(_value | map(mean(_value)))
+dict {
+    pe1: dict{pe1.1: 2, pe1.2: 2}
+    pe2: dict{pe2.1: 2, pe2.2: 2}
+    pe3: dict{pe3.1: 2, pe3.2: 2}
+} # nested map filters work but are very verbose
+>>> data | recmap(2, mean(_value))
+dict {
+    pe1: dict{pe1.1: 2, pe1.2: 2}
+    pe2: dict{pe2.1: 2, pe2.2: 2}
+    pe3: dict{pe3.1: 2, pe3.2: 2}
+}
+# recmap is much clearer.
+
+
+
+
+

topK

+

Added in revision 3

+

Filter topK filters the collection to keep only the k highest values. This filter can be +applied to a timeseries or a dict.

+
    +
  • The first parameter is the k parameter, which is the number of values to keep in the filtered collection.

  • +
  • The second parameter is an expression that returns for each entry of the collection the value to compare. +The return type of this expression must be comparable (num, str, time, or duration)

  • +
+

Usable metavariables in the expression for timeseries are:

+
    +
  • _index or _1: index of the current element (starting at \(0\))

  • +
  • _time or _2: timestamp of the current element

  • +
  • _value or _3: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+

Usable metavariables in the expression for dicts are:

+
    +
  • _key or _1: key of the current element

  • +
  • _value or _2: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
>>> let data = `analytics:/path/to/some/*/data` | map(merge(_value))
+>>> data
+dict{
+    Ethernet49/1: dict{
+        in: 11.845057565692196
+        out: 20.816078774499992
+    }
+    Ethernet49/5: dict{
+        in: 4.021321282808746
+        out: 8.868898231943206
+    }
+    Ethernet51/1: dict{
+        in: 2.1800167411644353
+        out: 2.413745251460854
+    }
+    Ethernet51/2: dict{
+        in: 3.126216167169341
+        out: 26.05024018915018
+    }
+    Ethernet51/3: dict{
+        in: 54.1046901332212
+        out: 5.035469519006775
+    }
+    Ethernet51/4: dict{
+        in: 7.313228804713885
+        out: 4.899238295809337
+    }
+    Ethernet8: dict{
+        in: 0
+        out: 71.6547381850231
+    }
+    Management1: dict{
+        in: 6.139184309225689
+        out: 0.7010378175218949
+    }
+    Port-Channel512: dict{
+        in: 7.864572656164906
+        out: 14.724350983923758
+    }
+    Port-Channel532: dict{
+        in: 16.652391153117858
+        out: 9.562088032011452
+    }
+}
+>>> data | topK(2, _value["in"])
+dict{
+    Ethernet51/3: dict{
+        in: 54.1046901332212
+        out: 5.035469519006775
+    }
+    Port-Channel532: dict{
+        in: 16.652391153117858
+        out: 9.562088032011452
+    }
+}
+>>> data | map(_value["in"]) | topK(2, _value)
+dict{
+    Ethernet51/3: 54.1046901332212
+    Port-Channel532: 16.652391153117858
+}
+
+
+
+
+

bottomK

+

Added in revision 3

+

Filter bottomK filters the collection to keep only the k lowest values. This filter can be +applied to a timeseries or a dict.

+
    +
  • The first parameter is the k parameter, which is the number of values to keep in the filtered collection.

  • +
  • The second parameter is an expression that returns for each entry of the collection the value to compare. +The return type of this expression must be comparable (num, str, time, or duration)

  • +
+

Usable metavariables in the expression for timeseries are:

+
    +
  • _index or _1: index of the current element (starting at \(0\))

  • +
  • _time or _2: timestamp of the current element

  • +
  • _value or _3: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+

Usable metavariables in the expression for dicts are:

+
    +
  • _key or _1: key of the current element

  • +
  • _value or _2: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
>>> let data = `analytics:/path/to/some/*/data` | map(merge(_value))
+>>> data
+dict{
+    Ethernet49/1: dict{
+        in: 11.845057565692196
+        out: 20.816078774499992
+    }
+    Ethernet49/5: dict{
+        in: 4.021321282808746
+        out: 8.868898231943206
+    }
+    Ethernet51/1: dict{
+        in: 2.1800167411644353
+        out: 2.413745251460854
+    }
+    Ethernet51/2: dict{
+        in: 3.126216167169341
+        out: 26.05024018915018
+    }
+    Ethernet51/3: dict{
+        in: 54.1046901332212
+        out: 5.035469519006775
+    }
+    Ethernet51/4: dict{
+        in: 7.313228804713885
+        out: 4.899238295809337
+    }
+    Ethernet8: dict{
+        in: 0
+        out: 71.6547381850231
+    }
+    Management1: dict{
+        in: 6.139184309225689
+        out: 0.7010378175218949
+    }
+    Port-Channel512: dict{
+        in: 7.864572656164906
+        out: 14.724350983923758
+    }
+    Port-Channel532: dict{
+        in: 16.652391153117858
+        out: 9.562088032011452
+    }
+}
+>>> data | bottomK(2, _value["in"])
+dict{
+    Ethernet51/1: dict{
+        in: 2.1800167411644353
+        out: 2.413745251460854
+    }
+    Ethernet8: dict{
+        in: 0
+        out: 71.6547381850231
+    }
+}
+>>> data | map(_value["in"]) | bottomK(2, _value)
+dict{
+    Ethernet51/1: 2.1800167411644353
+    Ethernet8: 0
+}
+
+
+
+
+

deepmap

+

Added in revision 1

+

Filter deepmap returns a timeseries or dict containing the results of the expression +passed as parameter applied to each entry of the filtered timeseries or dict, which can +contain nested timeseries or dicts.

+
    +
  • The first and only parameter is the expression. Its value can be of any type after evaluation.

  • +
  • Metavariables are applicable to the collection containing the leaf node to which the expression is applied, which can be nested under several layers.

  • +
+

Usable metavariables in the expression for timeseries are:

+
    +
  • _index or _1: index of the current element (starting at \(0\))

  • +
  • _time or _2: timestamp of the current element

  • +
  • _value or _3: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+

Usable metavariables in the expression for dicts are:

+
    +
  • _key or _1: key of the current element

  • +
  • _value or _2: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
>>> `analytics:/path/to/*/data/with/wildcard`[3]
+dict {
+    pathElement1: timeseries{t1:1, t2:2, t3:3, t4:4}
+    pathElement2: timeseries{t5:5, t6:6, t7:7, t8:8}
+    pathElement3: timeseries{dict{k10:10}, dict{k11:11}}
+}
+>>> _ | deepmap(_value + 1)
+dict {
+    pathElement1: timeseries{t1:2, t2:3, t3:4, t4:5}
+    pathElement2: timeseries{t5:6, t6:7, t7:8, t8:9}
+    pathElement3: timeseries{dict{k10:11}, dict{k11:12}}
+} # recursion depth can be different between branches, deepmap will recurse as long as the value is either a dict or timeseries
+
+
+
+
+

resample

+

Added in revision 1

+

Filter resample returns a timeseries resampled with the given duration as constant interval. +CloudVision timeseries are state-based, so any value in the output timeseries will be the latest +value prior to the output timestamp in the original timeseries.

+
    +
  • The first and only parameter, of type duration, specifies the interval of the output timeseries.

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+    2019-08-31 00:00:00: 13
+    2019-08-31 00:06:23: 1
+    2019-08-31 00:08:29: 2
+    2019-08-31 00:11:43: 200
+}
+>>> _ | resample(2m)
+timeseries{
+    2019-08-31 00:00:00: 13
+    2019-08-31 00:02:00: 13
+    2019-08-31 00:04:00: 13
+    2019-08-31 00:06:00: 13
+    2019-08-31 00:08:00: 13
+    2019-08-31 00:10:00: 2
+    2019-08-31 00:12:00: 200
+}
+
+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/doc/functions.html b/doc/functions.html new file mode 100644 index 0000000..2f6cc64 --- /dev/null +++ b/doc/functions.html @@ -0,0 +1,1720 @@ + + + + + + + General functions — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

General functions

+
+

now

+

Added in revision 1

+

Function now returns the current time. Time is constant across all the script, so that all operations and queries have the same reference time.

+
>>> now()
+2019-09-01T14:05:10Z
+
+
+
+
+

length

+

Added in revision 1

+

Function length returns the number of elements in a str, a timeseries or a dict.

+ +
>>> length(`analytics:/path/to/data`[0])
+1
+
+
+
+
+

merge

+

Added in revision 1

+

Function merge returns a union of all the dicts contained in a timeseries. In case of key collision, the latest entry is kept.

+ +
>>> `analytics:/path/to/data`[1]
+timeseries{
+        tstamp1: dict{key1: val1, key2: val2, key3: val3}
+        tstamp2: dict{key1: val4, key2: val5}
+}
+>>> merge(_)
+dict{key1: val4, key2: val5, key: val3}
+
+
+
+
+

deletes

+

Added in revision 1

+

Function deletes returns a timeseries of dicts used as sets of the delete keys from the input timeseries. +Only works with unfiltered timeseries, as most filters remove the deletes entries. An empty dict as value means the update is a DeleteAll

+
    +
  • The first and only parameter is the timeseries

  • +
+
>>> deletes(`analytics:/path/to/data`[200])
+timeseries{
+        tstamp1: dict{key1: nil, key2: nil}
+        tstamp2: dict{} # this deletes all keys
+}
+
+
+
+
+

equal

+

Added in revision 1

+

Function equal performs a cross-type equality check on the given arguments using type coercions.

+
    +
  • The first argument is a value of any type

  • +
  • The second argument is a value of any type

  • +
+
>>> equal("1", 1)
+true
+>>> equal(1, true)
+true
+>>> equal(0, true)
+false
+
+
+
+
+

complexKey

+

Added in revision 1

+

Function complexKey parses a string containing a literal or json object. +Numerical values without floating-point will produce an integer. +Numerical values with a floating point will produce a float64 (num). +Boolean literals will produce a boolean (bool). +Values surrounded with {} or [] will be parsed as JSON. +For all cases except float64 (num) and bool, the returned value will be of type unknown (internal interpreter type), but can be used to access complex keys in dicts.

+
    +
  • The first and only parameter is the str to parse

  • +
+
>>> complexKey("1")
+int(1) # AQL type is unknown
+>>> complexKey("1.2")
+float64(1.2) # AQL type is num
+>>> complexKey("{\"key1\": 1, \"key2\": true}")
+{"key1":1,"key2":true}# AQL type is unknown
+>>> complexKey("[1, 2, true]")
+[1,2,true] # AQL type is unknown
+
+
+
+
+
+

Dicts functions

+
+

newDict

+

Added in revision 1

+

Function newDict returns a new empty dict.

+
>>> newDict()
+dict{}
+
+
+
+
+

dictRemove

+

Added in revision 1

+

Function dictRemove removes a given key from a dict.

+
    +
  • The first argument is the dict

  • +
  • The second argument is the key to remove

  • +
+
>>> let d = newDict()
+>>> d["key"] = 1
+>>> d["key2"] = 2
+>>> d
+dict{"key": 1, "key2": 2}
+>>> dictRemove(d, "key")
+>>> d
+dict{"key2": 2}
+
+
+
+
+

dictHasKey

+

Added in revision 1

+

Function dictHasKey returns true if a dict contains the specified key, false if it doesn’t.

+
    +
  • The first parameter is the dict

  • +
  • The second parameter is the key

  • +
+
>>> let d = newDict()
+>>> d["key"] = 1
+>>> d["key2"] = 2
+>>> d
+dict{"key": 1, "key2": 2}
+>>> dictHasKey(d, "key")
+true
+>>> dictHasKey(d, "key3")
+false
+
+
+
+
+

dictKeys

+

Added in revision 1

+

Function dictKeys returns a timeseries with the list of keys in a dict.

+
    +
  • The first and only parameter is the dict

  • +
+
>>> let d = newDict()
+>>> d["key"] = 1
+>>> d["key2"] = 2
+>>> d
+dict{"key": 1, "key2": 2}
+>>> dictKeys(d)
+timeseries{
+        0000-00-00 00:00:00.000000001: "key"
+        0000-00-00 00:00:00.000000002: "key2"
+}
+
+
+
+
+

unnestTimeseries

+

Added in revision 4

+

Function unnestTimeseries merges multiple timeseries nested in dicts, pushes them to the top level, +and returns a single flattened timeseries where the former top-level dicts are now nested within +the timeseries’ values. The input data is typically what a query using * wildcards would return.

+
    +
  • The first and only parameter is a dict that contains timeseries, either directly in the value, or nested in more levels of dict.

  • +
+
+Example
>>> let ts = `analytics:/Devices/*/versioned-data/interfaces/data/*/aggregate/hardware/xcvr/1m`[1m] | recmap(2, _value | field("temperature") | field("avg"))
+>>> let d = newDict() | setFields("AA", ts)
+>>> d
+dict{AA: dict{
+                HSH14280171: dict{
+                        Ethernet50: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 34.184087816704434
+                                2022-08-16 17:11:00 +0200 CEST: 34.18519049983288
+                        }
+                        Ethernet51: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 34.84759166533158
+                                2022-08-16 17:11:00 +0200 CEST: 34.84247911778802
+                        }
+                }
+                JAS17070003: dict{
+                        Ethernet50: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 30.65325390983907
+                                2022-08-16 17:11:00 +0200 CEST: 30.65664047328105
+                        }
+                        Ethernet51: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 27.58473123455124
+                                2022-08-16 17:11:00 +0200 CEST: 27.597529745280074
+                        }
+                }
+        }}
+>>> unnestTimeseries(d)
+timeseries{
+        start: 2022-08-16 17:10:21.217673 +0200 CEST
+        end: 2022-08-16 17:11:21.217673 +0200 CEST
+        2022-08-16 17:10:00 +0200 CEST: dict{AA: dict{
+                        HSH14280171: dict{
+                                Ethernet50: 34.184087816704434
+                                Ethernet51: 34.84759166533158
+                        }
+                        JAS17070003: dict{
+                                Ethernet50: 30.65325390983907
+                                Ethernet51: 27.58473123455124
+                        }
+                }}
+        2022-08-16 17:11:00 +0200 CEST: dict{AA: dict{
+                        HSH14280171: dict{
+                                Ethernet50: 34.18519049983288
+                                Ethernet51: 34.84247911778802
+                        }
+                        JAS17070003: dict{
+                                Ethernet50: 30.65664047328105
+                                Ethernet51: 27.597529745280074
+                        }
+                }}
+}
+>>> let b = `analytics:/Devices/JPE17191574/versioned-data/interfaces/data/Ethernet50/aggregate/hardware/xcvr/1m`[2m] | field("voltage") | field("max")
+>>> let dd = newDict() | setFields("BB", b)
+>>> let ddd = newDict() | setFields("DD", ts, "EE", dd)
+>>> d["FF"]=ddd
+>>> d
+dict{
+        AA: dict{
+                HSH14280171: dict{
+                        Ethernet50: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 34.184087816704434
+                                2022-08-16 17:11:00 +0200 CEST: 34.18519049983288
+                        }
+                        Ethernet51: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 34.84759166533158
+                                2022-08-16 17:11:00 +0200 CEST: 34.84247911778802
+                        }
+                }
+                JAS17070003: dict{
+                        Ethernet50: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 30.65325390983907
+                                2022-08-16 17:11:00 +0200 CEST: 30.65664047328105
+                        }
+                        Ethernet51: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 27.58473123455124
+                                2022-08-16 17:11:00 +0200 CEST: 27.597529745280074
+                        }
+                }
+        }
+        FF: dict{
+                DD: dict{
+                        HSH14280171: dict{
+                                Ethernet50: timeseries{
+                                        start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                        end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                        2022-08-16 17:10:00 +0200 CEST: 34.184087816704434
+                                        2022-08-16 17:11:00 +0200 CEST: 34.18519049983288
+                                }
+                                Ethernet51: timeseries{
+                                        start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                        end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                        2022-08-16 17:10:00 +0200 CEST: 34.84759166533158
+                                        2022-08-16 17:11:00 +0200 CEST: 34.84247911778802
+                                }
+                        }
+                        JAS17070003: dict{
+                                Ethernet50: timeseries{
+                                        start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                        end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                        2022-08-16 17:10:00 +0200 CEST: 30.65325390983907
+                                        2022-08-16 17:11:00 +0200 CEST: 30.65664047328105
+                                }
+                                Ethernet51: timeseries{
+                                        start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                        end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                        2022-08-16 17:10:00 +0200 CEST: 27.58473123455124
+                                        2022-08-16 17:11:00 +0200 CEST: 27.597529745280074
+                                }
+                        }
+                }
+                EE: dict{BB: timeseries{
+                                start: 2022-08-16 17:14:06.794969 +0200 CEST
+                                end: 2022-08-16 17:16:06.794969 +0200 CEST
+                                2022-08-16 17:14:00 +0200 CEST: 3.2909
+                                2022-08-16 17:15:00 +0200 CEST: 3.2909
+                                2022-08-16 17:16:00 +0200 CEST: 3.2909
+                        }}
+        }
+}
+>>> unnestTimeseries(d)
+timeseries{
+        start: 2022-08-16 17:10:21.217673 +0200 CEST
+        end: 2022-08-16 17:16:06.794969 +0200 CEST
+        2022-08-16 17:10:00 +0200 CEST: dict{
+                AA: dict{
+                        HSH14280171: dict{
+                                Ethernet50: 34.184087816704434
+                                Ethernet51: 34.84759166533158
+                        }
+                        JAS17070003: dict{
+                                Ethernet50: 30.65325390983907
+                                Ethernet51: 27.58473123455124
+                        }
+                }
+                FF: dict{DD: dict{
+                                HSH14280171: dict{
+                                        Ethernet50: 34.184087816704434
+                                        Ethernet51: 34.84759166533158
+                                }
+                                JAS17070003: dict{
+                                        Ethernet50: 30.65325390983907
+                                        Ethernet51: 27.58473123455124
+                                }
+                        }}
+        }
+        2022-08-16 17:11:00 +0200 CEST: dict{
+                AA: dict{
+                        HSH14280171: dict{
+                                Ethernet50: 34.18519049983288
+                                Ethernet51: 34.84247911778802
+                        }
+                        JAS17070003: dict{
+                                Ethernet50: 30.65664047328105
+                                Ethernet51: 27.597529745280074
+                        }
+                }
+                FF: dict{DD: dict{
+                                HSH14280171: dict{
+                                        Ethernet50: 34.18519049983288
+                                        Ethernet51: 34.84247911778802
+                                }
+                                JAS17070003: dict{
+                                        Ethernet50: 30.65664047328105
+                                        Ethernet51: 27.597529745280074
+                                }
+                        }}
+        }
+        2022-08-16 17:14:00 +0200 CEST: dict{FF: dict{EE: dict{BB: 3.2909}}}
+        2022-08-16 17:15:00 +0200 CEST: dict{FF: dict{EE: dict{BB: 3.2909}}}
+        2022-08-16 17:16:00 +0200 CEST: dict{FF: dict{EE: dict{BB: 3.2909}}}
+}
+
+
+
+
+
+

Data Analysis Functions

+
+

groupby

+

Added in revision 1

+

Function groupby, applied to a timeseries, returns a dict with keys corresponding to the ‘group by field’ parameter, +and values corresponding to the associate method and field.

+

The function takes 4 parameters:

+
    +
  • A timeseries of dicts to apply this function to

  • +
  • The name of the ‘group by field’ (a str)

  • +
  • The name of one of the supported associative methods (a str)

  • +
  • The name of the field whose values will be operated on by the associative method (a str)

  • +
+

The entries in the timeseries are grouped by the values of the field from parameter 1. +For each entry, the value corresponding to the associative field of parameter 4 is obtained. +This results in a map with entries of the following format:

+
    +
  • key: values of the ‘group by field’

  • +
  • value: lists of values of the ‘associative field’

  • +
+

On each of these lists, the following associative methods can be applied:

+
    +
  • count: returns the length of the list (i.e. the item count)

  • +
  • max: returns the max entry in the list

  • +
  • mean: returns the mean of the values in the list

  • +
  • min: returns the min entry in the list

  • +
  • sum: returns the sum of the entries in the list

  • +
+
>>> `analytics:/path/to/data`[3]
+timeseries{
+        tstamp1: dict{"name": "name1", "value": 1}
+        tstamp2: dict{"name": "name2", "value": 10}
+        tstamp3: dict{"name": "name1", "value": 2}
+        tstamp4: dict{"name": "name2", "value": 11}
+}
+>>> let ts = _
+>>> groupby(ts, "name", "mean", "value")
+dict{
+        "name1": 1.5
+        "name2": 10.5
+}
+>>> groupby(ts, "name", "count", "value")
+dict{
+        "name1": 2
+        "name2": 2
+}
+>>> groupby(ts, "name", "sum", "value")
+dict{
+        "name1": 3
+        "name2": 21
+}
+
+
+
+
+

histogram

+

Added in revision 1

+

Function histogram, for a given timeseries of non-dict values, returns a dict with entries of the following format:

+
    +
  • key: value in the timeseries (range if a timeseries of num values)

  • +
  • value: time-weighted frequency in the timeseries

  • +
+

Arguments:

+
    +
  • A timeseries of non-dict values is the only argument to this function

  • +
+
>>> `analytics:/path/to/data`[3] | field("strfield")
+timeseries{
+        start: 2019-08-31 00:00:00
+        end: 2019-08-31 00:12:00
+        2019-08-31 00:00:00: "string1"
+        2019-08-31 00:01:00: "string2"
+        2019-08-31 00:10:00: "string1"
+        2019-08-31 00:11:00: "string1"
+}
+>>> histogram(_)
+dict{
+        "string1": 0.25
+        "string2": 0.75
+} # the count is weighted accordingly to the intervals
+>>> `analytics:/path/to/data`[5] | field("numfield")
+timeseries{
+        start: 2019-08-31 00:00:00
+        end: 2019-08-31 01:00:00
+        2019-08-31 00:00:00: 1
+        2019-08-31 00:01:00: 1.01
+        2019-08-31 00:10:00: 1.011
+        2019-08-31 00:30:00: 5.2
+        2019-08-31 00:44:00: 5.22
+        2019-08-31 00:56:00: 5.23
+}
+>>> histogram(_)
+dict{
+        "1.0-1.011": 0.5
+        "5.2-5.23": 0.5
+} # the count is weighted accordingly to the intervals
+
+
+
+
+

dhistogram

+

Added in revision 1

+

Function dhistogram, has a similar behaviour as histogram but its result is not time-weighted. +For a given timeseries of non-dict values, returns a dict with entries of the following format:

+ +

Arguments:

+

A timeseries of non-dict values is the only argument to this function

+
>>> `analytics:/path/to/data`[3] | field("strfield")
+timeseries{
+        start: 2019-08-31 00:00:00
+        end: 2019-08-31 00:05:00
+        2019-08-31 00:00:00: "string1"
+        2019-08-31 00:01:00: "string2"
+        2019-08-31 00:10:00: "string1"
+        2019-08-31 00:11:00: "string1"
+} # the count does not depend of the time intervals between the updates
+>>> dhistogram(_)
+dict{
+        "string1": 3
+        "string2": 1
+} # the count does not depend of the time intervals between the updates
+>>> `analytics:/path/to/data`[5] | field("numfield")
+timeseries{
+        2019-08-31 00:00:00: 1
+        2019-08-31 00:01:00: 1.01
+        2019-08-31 00:10:00: 1.011
+        2019-08-31 00:30:00: 5.2
+        2019-08-31 00:44:12: 5.22
+        2019-08-31 02:01:34: 5.23
+}
+>>> dhistogram(_)
+dict{
+        "1.0-1.011": 3
+        "5.2-5.23": 3
+} # the count does not depend of the time intervals between the updates
+
+
+
+
+

aggregate

+

Added in revision 4

+

Function aggregate merges multiple timeseries contained in a dict (like the result of a +wildcarded query) using the associative method specified in the second parameter. The dict must +contain timeseries, all of which must contain identical timestamps.

+

If one of the timeseries is empty, it will be ignored.

+

If some values’ timestamps are not matched in all the other non-empty timeseries of the dict, +these timestamp-value pairs will not be present in the output timeseries.

+

aggregate returns a simple timeseries with the aggregated data of all the input timeseries.

+
    +
  • The first argument is the dict containing timeseries to aggregate

  • +
  • The second argument is the name of the associative method to apply

  • +
+

Like with groupby, the following associative methods can be applied:

+
    +
  • count: returns the length of the list (i.e. the item count)

  • +
  • max: returns the max entry in the list (requires the timeseries to be numerical)

  • +
  • mean: returns the mean of the values in the list (requires the timeseries to be numerical)

  • +
  • min: returns the min entry in the list (requires the timeseries to be numerical)

  • +
  • sum: returns the sum of the entries in the list (requires the timeseries to be numerical)

  • +
+
>>> let data = `analytics:/Devices/*/versioned-data/interfaces/data/*/aggregate/hardware/xcvr/15m`[1h]
+>>> let avg = data | recmap(2, _value | field("temperature") | field("avg"))
+>>> avg
+        JPE123456: dict{
+                Ethernet1: timeseries{
+                        start: 2021-11-09 13:02:18.923904 +0000 GMT
+                        end: 2021-11-09 13:32:18.923904 +0000 GMT
+                        2021-11-09 13:00:00 +0000 GMT: 28.64315689104305
+                        2021-11-09 13:15:00 +0000 GMT: 28.64771549594622
+                        2021-11-09 13:30:00 +0000 GMT: 28.647003241959368
+                }
+                Ethernet2: timeseries{
+                        start: 2021-11-09 13:02:18.923904 +0000 GMT
+                        end: 2021-11-09 13:32:18.923904 +0000 GMT
+                        2021-11-09 13:00:00 +0000 GMT: 26.52073192182222
+                        2021-11-09 13:15:00 +0000 GMT: 26.57132998707
+                        2021-11-09 13:30:00 +0000 GMT: 26.562415784963335
+                }
+                [...]
+        }
+        JPE654321: dict{
+                Ethernet1: timeseries{
+                        start: 2021-11-09 13:02:18.923904 +0000 GMT
+                        end: 2021-11-09 13:32:18.923904 +0000 GMT
+                        2021-11-09 13:00:00 +0000 GMT: 27.872056741171672
+                        2021-11-09 13:15:00 +0000 GMT: 26.422506200403397
+                        2021-11-09 13:30:00 +0000 GMT: 27.889330661612725
+                }
+                Ethernet2: timeseries{
+                        start: 2021-11-09 13:02:18.923904 +0000 GMT
+                        end: 2021-11-09 13:32:18.923904 +0000 GMT
+                        2021-11-09 13:00:00 +0000 GMT: 25.501376131906685
+                        2021-11-09 13:15:00 +0000 GMT: 24.06172043150084
+                        2021-11-09 13:30:00 +0000 GMT: 25.520567819910998
+                }
+                [...]
+        }
+        [...]
+>>> let deviceAvg = avg | map(aggregate(_value, "mean"))
+>>> deviceAvg
+        JPE123456: timeseries{
+                start: 2021-11-09 13:02:18.923904 +0000 GMT
+                end: 2021-11-09 13:32:18.923904 +0000 GMT
+                2021-11-09 13:00:00 +0000 GMT: 29.46781924765547
+                2021-11-09 13:15:00 +0000 GMT: 28.739134103556832
+                2021-11-09 13:30:00 +0000 GMT: 29.756429823529587
+        }
+        JPE654321: timeseries{
+                start: 2021-11-09 13:02:18.923904 +0000 GMT
+                end: 2021-11-09 13:32:18.923904 +0000 GMT
+                2021-11-09 13:00:00 +0000 GMT: 27.581944406432633
+                2021-11-09 13:15:00 +0000 GMT: 27.60952274150811
+                2021-11-09 13:30:00 +0000 GMT: 27.60470951346135
+        }
+        [...]
+>>> aggregate(deviceAvg, "mean") # average temp accross all interfaces of all devices
+timeseries{
+        start: 2021-11-09 13:02:18.923904 +0000 GMT
+        end: 2021-11-09 13:32:18.923904 +0000 GMT
+        2021-11-09 13:00:00 +0000 GMT: 33.261383897237806
+        2021-11-09 13:15:00 +0000 GMT: 33.16722874112898
+        2021-11-09 13:30:00 +0000 GMT: 33.37487749955894
+}
+
+
+
+
+
+

Math functions

+
+

abs

+

Added in revision 1

+

Function abs returns the absolute value (num) of the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the absolute value \(\lvert x \lvert\) is wanted

  • +
+
>>> abs(-11)
+11
+>>> abs(200)
+200
+
+
+
+
+

ceil

+

Added in revision 1

+

Function ceil returns the closest integer (num) succeeding the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the ceil \(\lceil x \rceil\) is wanted

  • +
+
>>> ceil(12)
+12
+>>> ceil(12.1)
+13
+>>> ceil(-12.1)
+-12
+
+
+
+
+

floor

+

Added in revision 1

+

Function floor returns the closest integer (num) preceding the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the floor \(\lfloor x \rfloor\) is wanted

  • +
+
>>> floor(3)
+3
+>>> floor(3.2)
+3
+>>> floor(-3.2)
+-4
+
+
+
+
+

trunc

+

Added in revision 1

+

Function trunc returns the truncated (num) given value.

+
    +
  • The first and only argument is the value (num) to be truncated

  • +
+
>>> trunc(2.6)
+2
+>>> trunc(-2.49)
+-2
+
+
+
+
+

exp

+

Added in revision 1

+

Function exp returns the exponential (num) of the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the exp \(e^x\) is wanted

  • +
+
>>> exp(0)
+1
+>>> exp(12.1)
+179871.86225375105
+
+
+
+
+

factorial

+

Added in revision 1

+

Function factorial returns the factorial (num) of the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the factorial \(x!\) is wanted

  • +
+
>>> factorial(3)
+6
+
+
+
+
+

gcd

+

Added in revision 1

+

Function gcd returns the greatest common divisor (num) of two given integers.

+
    +
  • The first two arguments are the integers (num) of which the GCD is wanted

  • +
+
>>> gcd(25, 30)
+5
+
+
+
+
+

log

+

Added in revision 1

+

Function log returns the natural log (num) of the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the natural log \(log_e x\) is wanted

  • +
+
>>> log(10)
+2.302585092994046
+
+
+
+
+

log10

+

Added in revision 1

+

Function log10 returns the decimal log (num) of the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the decimal log \(log_{10} x\) is wanted

  • +
+
>>> log10(10)
+1
+
+
+
+
+

pow

+

Added in revision 1

+

Function pow returns the first given value (num) to the power of the second given value.

+
    +
  • The two arguments are the values \(x\) (num), \(y\) (num) used to compute \(x^y\)

  • +
+
>>> pow(3, 2)
+9
+>>> pow(9, 1/2)
+3
+
+
+
+
+

round

+

Added in revision 1

+

Function round returns the rounded (num) given value.

+
    +
  • The first and only argument \(x\) is the value used to compute \(\lfloor x\rceil\) i.e. the rounded value (num)

  • +
+
>>> round(2.5)
+3
+>>> round(2.49)
+2
+
+
+
+
+

sqrt

+

Added in revision 1

+

Function sqrt returns the square root (num) of the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the square root \(\sqrt{x}\) is wanted

  • +
+
>>> sqrt(9)
+3
+
+
+
+
+

max

+

Added in revision 1

+

Function max returns the max value (num) in a timeseries or a dict.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> max(_)
+200
+
+
+
+
+

min

+

Added in revision 1

+

Function min returns the min value (num) in a timeseries or a dict.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> min(_)
+1
+
+
+
+
+

formatInt

+

Added in revision 4

+

Function formatInt formats a num into a str using the specified base. The num will be treated +as an integer and any decimal part will be truncated.

+
    +
  • The first argument is the num to convert

  • +
  • The second argument is the base (num)

  • +
+
>>> formatInt(4, 2)
+100
+>>> formatInt(4.5, 2)
+100
+>>> formatInt(33, 2)
+100001
+>>> formatInt(15, 16)
+f
+>>> formatInt(29, 16)
+1d
+>>> type(_)
+str
+
+
+
+
+

formatFloat

+

Added in revision 4

+

Function formatFloat formats a num (float64) into a str, according to the specified format and +precision.

+
    +
  • The first argument is the num to convert

  • +
  • The second argument is a str of one letter describing the format:

    +
    +
      +
    • 'b': binary exponent

    • +
    • 'e': decimal exponent

    • +
    • 'f': no exponent

    • +
    • 'x': hexadecimal fraction and binary exponent

    • +
    +
    +
  • +
  • The third argument is :ref:a num specifying the precision, i.e. the number of digits after the decimal +point

  • +
+
>>> formatFloat(15682.8729, "e", 10)
+1.5682872900e+04
+>>> formatFloat(15682.8729, "f", 10)
+15682.8729000000
+>>> formatFloat(15, "x", 3)
+0x1.e00p+03
+>>> formatFloat(15, "b", 3)
+8444249301319680p-49
+>>> type(_)
+str
+
+
+
+
+
+

Stats functions

+
+

dsum

+

Added in revision 1

+

Function dsum returns the non-weighted sum of values (num) in a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dsum(_)
+216
+
+
+
+
+

dmean

+

Added in revision 1

+

Function dmean returns the non-weighted mean value (num) of a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dmean(_)
+54
+
+
+
+
+

dmedian

+

Added in revision 1

+

Function dmedian returns the non-weighted median (num) of a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dmedian(_)
+2
+
+
+
+
+

dpercentile

+

Added in revision 1

+

Function dpercentile returns the non-weighted nth percentile (num) of a timeseries.

+
    +
  • The first argument is a timeseries containing plain num values

  • +
  • The second argument is a num specifying the percentile. If it is greater than 100 or lower than 0, the return value will be 0.

  • +
+
>>> let a = `analytics:/path/to/data`[3] | field("numfield")
+>>> a
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dpercentile(a, 50)
+2
+>>> dpercentile(a, 90)
+200
+
+
+
+
+

dvariance

+

Added in revision 1

+

Function dvariance returns the non-weighted statistical variance (num) of a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dvariance(_)
+9503.333333333334
+
+
+
+
+

dstddev

+

Added in revision 1

+

Function dstddev returns the non-weighted standard deviation (num) of a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> let a = `analytics:/path/to/data`[3] | field("numfield")
+>>> a
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dstddev(a)
+97.48504158758581
+>>> sqrt(dvariance(a))
+97.48504158758581
+
+
+
+
+

dskew

+

Added in revision 1

+

Function dskew returns the non-weighted skewness of distribution (num) for data in a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dskew(_)
+0.7431002727844832
+
+
+
+
+

dkurtosis

+

Added in revision 1

+

Function dkurtosis returns the non-weighted kurtosis of distribution (num) for data in a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dkurtosis(_)
+-1.6923313578244437
+
+
+
+
+

sum

+

Added in revision 1

+

Function sum returns the sum of the num values in a timeseries or a dict. +If applied to a timeseries, the result is time-weighted.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> sum(_)
+216
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> sum(d)
+216
+
+
+
+
+

mean

+

Added in revision 1

+

Function mean returns the mean of the num values in a timeseries or a dict. If applied to a timeseries, the result is time-weighted.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> mean(_)
+54 # will be different from dmean if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> mean(d)
+54
+
+
+
+
+

median

+

Added in revision 1

+

Function median returns the median of the num values in a timeseries or a dict. If applied to a timeseries, the result is time-weighted.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> median(_)
+2 # will be different from dmedian if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> median(d)
+2
+
+
+
+
+

percentile

+

Added in revision 1

+

Function percentile returns the time-weighted nth percentile (num) of a timeseries or a dict. +If applied to a timeseries, the result is time-weighted.

+
    +
  • The first argument is a timeseries or a dict containing plain num values

  • +
  • The second argument is a num specifying the percentile. If it is greater than \(100\) or lower than \(0\), the return value will be \(0\).

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> percentile(_, 90)
+200 # will be different from dpercentile if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> percentile(d, 90)
+200
+
+
+
+
+

variance

+

Added in revision 1

+

Function variance returns the statistical variance of the num values in a timeseries or a dict. +If applied to a timeseries, the result is time-weighted.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> variance(_)
+9503.333333333334 # will be different from dvariance if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> variance(d, 90)
+9503.33333333
+
+
+
+
+

stddev

+

Added in revision 1

+

Function stddev returns the standard deviation of the num values in a timeseries or a dict. +If applied to a timeseries, the result is time-weighted.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> stddev(_)
+97.485041588 # will be different from dstddev if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> stddev(d, 90)
+97.485041588
+
+
+
+
+

skew

+

Added in revision 1

+

Function skew returns the skewness of dist.n of the num values in a timeseries or a dict. +If applied to a timeseries, the result is time-weighted. If the timeseries has exactly one element, \(0\) is returned.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> skew(_)
+0.7431002727844832 # will be different from dskew if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> skew(d, 90)
+0.7431002727844832
+
+
+
+
+

kurtosis

+

Added in revision 1

+

Function kurtosis returns the kurtosis of dist.n of the num values in a timeseries or a dict. +If applied to a timeseries, the result is time-weighted. If the timeseries has exactly one element, \(0\) is returned.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> skew(_)
+0.7431002727844832 # will be different from dskew if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> skew(d, 90)
+0.7431002727844832
+
+
+
+
+

rate

+

Added in revision 1

+

Function rate returns a timeseries of rates computed from the initial timeseriesnum values.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        2019-08-31 00:00:00: 1
+        2019-08-31 00:01:00: 10
+        2019-08-31 00:02:00: 50
+        2019-08-31 00:03:00: 110
+        2019-08-31 00:04:00: 230
+}
+>>> rate(_)
+timeseries{
+        2019-08-31 00:00:00: 0.016666666666666666
+        2019-08-31 00:01:00: 0.15
+        2019-08-31 00:02:00: 0.6666666666666666
+        2019-08-31 00:03:00: 1
+        2019-08-31 00:04:00: 2
+}
+
+
+
+
+

linregression

+

Added in revision 3

+

Function linregression produces a linear fit of a timeseries of num.

+ +

It returns a dict with 4 entries slope, intercept, R2 and fit. The fit entry is a timeseries +with num values corresponding to the fitted line on the input timeseries’ timestamps. The slope +and intercept are in seconds.

+
>>> `analytics:/path/to/data`[5m] | field("numfield")
+timeseries{
+        start: 2021-10-14 13:57:55.000545 +0100 IST
+        end: 2021-10-14 14:02:55.000545 +0100 IST
+        2021-10-14 13:57:00 +0100 IST: 3.801633107494212e-05
+        2021-10-14 13:58:00 +0100 IST: 7.653320559746086e-05
+        2021-10-14 13:59:00 +0100 IST: 3.971200542852744e-05
+        2021-10-14 14:00:00 +0100 IST: 3.981215360110563e-05
+        2021-10-14 14:01:00 +0100 IST: 5.2121957107961934e-05
+        2021-10-14 14:02:00 +0100 IST: 3.838672949594982e-05
+}
+>>> linregression(_)
+dict{
+        R2: 0.06273653863866613
+        fit: timeseries{
+                start: 2021-10-14 13:57:00 +0100 IST
+                end: 2021-10-14 14:02:00 +0100 IST
+                2021-10-14 13:57:00 +0100 IST: 5.252194027605128e-05
+                2021-10-14 13:58:00 +0100 IST: 5.0485322987015024e-05
+                2021-10-14 13:59:00 +0100 IST: 4.844870569797877e-05
+                2021-10-14 14:00:00 +0100 IST: 4.641208840183708e-05
+                2021-10-14 14:01:00 +0100 IST: 4.4375471112800824e-05
+                2021-10-14 14:02:00 +0100 IST: 4.2338853823764566e-05
+        }
+        intercept: 55.47126937459381
+        slope: -3.39436215194667e-08
+}
+
+
+
+
+

ewlinregression

+

Added in revision 3

+

Function ewlinregression produces a linear fit of a timeseries of num using exponentially +decaying weights. The older the value in the timeseries the smaller the weight. The latest value is +always given a weight of \(1\).

+
    +
  • The first argument is the input timeseries of num

  • +
  • The second argument is the desired weight that a point with time x seconds in the past would have

  • +
  • the third argument is how long ago that time \(x\) is, in seconds

  • +
+

It returns a dict with 4 entries slope, intercept, R2 and fit. The fit entry is a timeseries +with num values corresponding to the fitted line on the input timeseries’ timestamps. The slope +and intercept are in seconds.

+
>>> `analytics:/path/to/data`[5m] | field("numfield")
+timeseries{
+        start: 2021-10-14 13:57:55.000545 +0100 IST
+        end: 2021-10-14 14:02:55.000545 +0100 IST
+        2021-10-14 13:57:00 +0100 IST: 3.801633107494212e-05
+        2021-10-14 13:58:00 +0100 IST: 7.653320559746086e-05
+        2021-10-14 13:59:00 +0100 IST: 3.971200542852744e-05
+        2021-10-14 14:00:00 +0100 IST: 3.981215360110563e-05
+        2021-10-14 14:01:00 +0100 IST: 5.2121957107961934e-05
+        2021-10-14 14:02:00 +0100 IST: 3.838672949594982e-05
+}
+>>> ewlinregression(_, 0.01, 100.0)
+dict{
+        R2: 0.34201204121343765
+        fit: timeseries{
+                start: 2021-10-14 14:03:00 +0100 IST
+                end: 2021-10-14 14:08:00 +0100 IST
+                2021-10-14 14:03:00 +0100 IST: 4.5425229615148055e-05
+                2021-10-14 14:04:00 +0100 IST: 4.4509288322558405e-05
+                2021-10-14 14:05:00 +0100 IST: 4.359334702641604e-05
+                2021-10-14 14:06:00 +0100 IST: 4.267740573382639e-05
+                2021-10-14 14:07:00 +0100 IST: 4.176146444123674e-05
+                2021-10-14 14:08:00 +0100 IST: 4.0845523145094376e-05
+        }
+        intercept: 24.94748623552414
+        slope: -1.526568822982724e-08
+}
+
+
+
+
+
+

String manipulation

+
+

strToUpper

+

Added in revision 1

+

Function strToUpper returns uppercase version of given str.

+
    +
  • The first and only parameter is a str to convert to uppercase

  • +
+
>>> strToUpper("ToUpper")
+"TOUPPER"
+
+
+
+
+

strToLower

+

Added in revision 1

+

Function strToLower returns lowercase version of given str.

+
    +
  • The first and only parameter is a str to convert to lowercase

  • +
+
>>> strToLower("ToLower")
+"TOLOWER"
+
+
+
+
+

strContains

+

Added in revision 1

+

Function strContains returns whether the first str contains the second str.

+
    +
  • Both arguments to the function are str

  • +
+
>>> strContains("thatistext", "is")
+true
+
+
+
+
+

strCount

+

Added in revision 1

+

Function strCount returns the number of occurrences of the second str in the first str.

+
    +
  • Both arguments to the function are str

  • +
+
>>> strCount("tertarter", "te")
+2
+
+
+
+
+

strIndex

+

Added in revision 1

+

Function strIndex returns the index of the first occurrence of the second str in the first str, and -1 if it is not present.

+
    +
  • Both arguments to the function are str

  • +
+
>>> strIndex("thatistext", "is")
+4
+
+
+
+
+

strReplace

+

Added in revision 1

+

Function strReplace returns a copy of the first str, where occurrences of the second str are replaced by the third str.

+
    +
  • The three arguments to the function are str

  • +
+
>>> strReplace("thatistext", "is", "was")
+"thatwastext"
+
+
+
+
+

strHasPrefix

+

Added in revision 1

+

Function strHasPrefix returns whether the first str starts with the second str.

+
    +
  • Both arguments to the function are str

  • +
+
>>> strHasPrefix("thatistext", "is")
+false
+>>> strHasPrefix("thatistext", "that")
+true
+
+
+
+
+

strHasSuffix

+

Added in revision 1

+

Function strHasSuffix returns whether the first str ends with the second str.

+
    +
  • Both arguments to the function are str

  • +
+
>>> strHasSuffix("thatistext", "xt")
+true
+
+
+
+
+

strSplit

+

Added in revision 1

+

Function strSplit returns a timeseries of str. The function splits the first str into substrings, separated by the second str.

+
    +
  • Both arguments to the function are str

  • +
+
>>> strSplit("that./is.text", "./")
+timeseries{
+        0000-00-00 00:00:00.000000001: "that"
+        0000-00-00 00:00:00.000000002: "is.text"
+}
+
+
+
+
+

strCut

+

Added in revision 4

+

Function strCut returns the portion of a str between two indexes (excluding ending index).

+

Negative indexes start from the end of the input str.

+
    +
  • The first argument (str) is the string from which the portion is returned

  • +
  • The second argument (num) is the starting index

  • +
  • The third argument (num) is the ending index

  • +
+
>>> strCut("0123456789", 1, 4)
+123
+>>> strCut("0123456789", -8, -2)
+234567
+>>> strCut("abcd", 1, 3)
+bc
+
+
+
+
+

reFindAll

+

Added in revision 1

+

Function reFindAll returns a timeseries of str which contains matches of the second str (regex) in the first str.

+
    +
  • Both arguments to the function are str

  • +
+
>>> reFindAll("i am a string with text", "i[a-z]+")
+timeseries{
+        0000-00-00 00:00:00.000000001: "ing"
+        0000-00-00 00:00:00.000000002: "ith"
+}
+
+
+
+
+

reMatch

+

Added in revision 1

+

Function reMatch returns whether the first str contains matches of the second str (regex).

+
    +
  • Both arguments to the function are str

  • +
+
>>> reMatch("i am a string with text", "i[a-z]+")
+true
+
+
+
+
+

reFindCaptures

+

Added in revision 1

+

Function reFindCaptures returns a timeseries of str lists. Each list contains the full match followed by each capture

+
    +
  • Both arguments to the function are str. The first one is the str to match, and the second is the regular expression

  • +
+
>>> reFindCaptures("foobarbaztootartaz", "foo")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["foo"]
+}
+>>> reFindCaptures("foobarbaztootartaz", "(foo)")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["foo", "foo"]
+}
+>>> reFindCaptures("foobarbaztootartaz", "f(oo)")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["foo", "oo"]
+}
+>>> reFindCaptures("foobarbaztootartaz", "(oo)")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["oo", "oo"]
+        0000-00-00 00:00:00.000000002: ["oo", "oo"]
+}
+>>> reFindCaptures("foobarbaztootartaz", "[ft](oo)")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["foo", "oo"]
+        0000-00-00 00:00:00.000000002: ["too", "oo"]
+}
+>>> reFindCaptures("foobarbaztootartaz", "([ft])(oo)")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["foo", "f", "oo"]
+        0000-00-00 00:00:00.000000002: ["too", "t", "oo"]
+}
+>>> reFindCaptures("foobarbaztootartaz", "[ft](oo).*(az)")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["foobarbaztootartaz", "oo", "az"]
+}
+
+
+
+
+
+

CLI-only Functions

+
+

Warning

+

The functions described in this section can only be used in CLI. They cannot be called from a service +or through a Web interface.

+
+
+

help

+

Added in revision 1

+

Function help returns the help of all filters and functions as a formatted str.

+
>>> help()
+# Functions
+## now
+- Function `now` returns the current time. Time is constant across all the script, so that all operations and queries have the same reference time
+[...]
+
+
+
+
+

dump

+

Added in revision 1

+

Function dump attempts to dump variables from the interpreter into a file.

+
    +
  • The first and only argument is the path to the file (str)

  • +
+
>>> let myVar = 2
+>>> dump("file.dump")
+
+
+
+
+

load

+

Added in revision 1

+

Function load attempts to load variables into the interpreter from a file.

+
    +
  • The first and only argument is the path to the file (str)

  • +
+
>>> load("file.dump")
+true
+>>> myVar
+2
+>>> let myVar = 5
+>>> if load("file.dump") {
+...     myVar
+... }
+2
+
+
+
+
+

plot

+

Added in revision 1

+

Function plot plots a timeseries or dict of num values.

+
    +
  • The first argument is the timeseries or dict of num values to plot

  • +
  • The second argument (optional) is the path (str) to the image PNG file to write the plot to. If not specified, defaults to plot.png

  • +
+
>>> plot(myTimeseriesOrDict, "myplotimg.png")
+
+
+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/doc/language_reference.html b/doc/language_reference.html new file mode 100644 index 0000000..39d334e --- /dev/null +++ b/doc/language_reference.html @@ -0,0 +1,1535 @@ + + + + + + + Quick overview — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Quick overview

+

The syntax in AQL is similar to that of scripting languages. However, it is designed to be used like +a query language. Its types are specifically designed to match the data structures of the CloudVision +database.

+

In interactive mode, each statement is executed independently, and therefore each of their values is +printed when run. However, when running an AQL script with multiple statements from the CloudVision +dashboard or from a file, the only displayed value is the value of the script as a whole, which is the +value of the last statement in the script.

+

Example in interactive mode:

+
>>> let a = 1
+>>> a
+1
+>>> a + 2
+3
+
+
+

Running it in one go:

+
let a = 1
+a
+a+2
+
+
+

This script simply returns 3.

+

The main features of the language are:

+
    +
  • Queries: quickly fetch data from the CloudVision database

  • +
  • Filters: efficiently format and extract the appropriate data from the query results

  • +
+

Example:

+
# This first statement performs a query to get aggregate data for each interface of device SSJ123456
+let hardwareAggs = `analytics:/Devices/SSJ123456/versioned-data/interfaces/data/*/aggregate/hardware/xcvr/1m`
+
+# This statement uses filters to extract the average temperature for each interface
+let temperaturePerIntf = hardwareAggs | map((_value | field("temperature") | field("avg"))[0])
+
+# This last statement computes the average temperature across all the interfaces in the device and is the
+# return value of the script
+mean(temperaturePerIntf)
+
+
+
+
+

Basic statements

+

Statements are separated by new lines. +Comments start with a # so anything following # is not part of a statement.

+
>>> 1 + 2
+3
+>>> 1 * (2+1) / (2*5)
+0.3
+
+
+

Values can be assigned into a variable using the let keyword.

+
>>> let myVar1 = 9 % 5 # variable myVar1 contains 4
+
+
+

The last statement’s value is stored in the metavariable _. However, some statements like +variable assignments do not have a value, so they don’t change the value of _.

+
>>> _
+0.3
+
+
+

Variables can be used in expressions.

+
>>> 5 * _ + 12.4 + myVar1 + -1 - 1
+15.9
+>>> _ + 1
+16.9
+
+
+

Variable names must begin with a letter (lower or uppercase). The rest of the name can contain letters +(lower or uppercase), digits, and underscores.

+
+

Warning

+

Variable names that are prefixed with an underscore are metavariables and are set by the interpreter. +These cannot be reassigned (read-only) but they can be used.

+

More details about metavariables defined by named wildcards (<wcName>) in the Named wildcards +section of this document.

+
+
>>> let myVar_Name2 = 1
+>>> myVar_Name2
+1
+>>> let someData = `<d>:/Devices/some/data/path`
+>>> _d
+JPE123456
+>>> let _metavar = 12
+error: input:1:1: illegal variable name: _metavar
+
+
+
+
+

Types

+
+

num

+

The num type is a float64 and is the only numerical type. AQL does not have a native integer type.

+

This type can be defined through literals, either an integer or a floating-point value:

+
>>> let i = 12
+>>> let j = 15.42
+>>> i+j
+27.42
+
+
+
+
+

bool

+

The bool type is a boolean and can either be true or false.

+

The syntax of its literal is either the true or false keyword.

+
>>> let b = true
+>>> b && false
+false
+
+
+
+
+

str

+

The str type is a string of characters.

+

The syntax of its literal is any string of character surrounded with double-quotes. To insert a double-quote +within the literal, it can be prefixed with a backslash \\. +Single-quote strings are not supported in AQL.

+

All types can be cast to str.

+
>>> "Don't \"panic\"!"
+Don't "panic"!
+>>> str(12.0) # this is a cast from num to str
+12
+>>> str(12.1)
+12.1
+
+
+
+
+

time

+

The time type holds a timestamp. It is the key type in the timeseries type returned by queries.

+

There are no literals for time, but it can be cast from a str following the syntax described in RFC 3339.

+
>>> let t = time("2006-01-02T15:04:05+07:00")
+>>> t
+2006-01-02 15:04:05 +0700 +0700
+>>> t + 15s
+2006-01-02 15:04:20 +0700 +0700
+
+
+
+
+

duration

+

The duration type defines a time interval. It can be used to define a time range of data to get +in queries, and it can be added to or subtracted from time values.

+

The syntax of its literal is a signed (or not) sequence of decimal numbers followed by a unit suffix. +It can also be composed of multiple values in different time units: 300ms, -1.5h, 2h45m.

+

Valid time units are:

+
    +
  • ns (nanosecond)

  • +
  • us (microsecond)

  • +
  • ms (millisecond)

  • +
  • s (second)

  • +
  • m (minute)

  • +
  • h (hour)

  • +
+
>>> 5h30ms
+5h0m0.03s
+>>> 7 * 24h # week
+168h0m0s
+>>> time("2006-01-02T15:04:05+07:00") + 5h15s
+2006-01-02 15:04:20 +0700 +0700
+
+
+
+
+

type

+

The type type holds type information. Any value can be cast to type to know its type.

+

The syntax of its literal is any type name without any quotes or delimiter.

+
>>> let a = 2
+>>> type(a) # This is a cast to type `type`
+num
+>>> type("Hello World!")
+str
+>>> type(str)
+type
+>>> type("Don't panic!") == bool
+false
+
+
+
+
+

timeseries

+

The timeseries type is a list of values (of any type), indexed by timestamps (time values). +Its values can be accessed either by num index or time index. If there is no exact match for the +specified time, accessing its value will return the latest entry before that time.

+
+

Note

+

There are no literals for timeseries and they cannot be manually created. It can be returned by some +functions (see the documentation for Standard Library functions), and all AQL queries return a timeseries +(which can be contained in a dict, see sections about Wildcards).

+
+
>>> let a = `analytics:/Devices/JPE17191574/versioned-data/interfaces/data/Ethernet50/aggregate/hardware/xcvr/1m`[5m] | field("temperature") | field("avg")
+>>> a
+timeseries{
+    start: 2021-10-26 14:32:17.167535 +0100 IST
+    end: 2021-10-26 14:37:17.167535 +0100 IST
+    2021-10-26 14:32:00 +0100 IST: 26.77301344308594
+    2021-10-26 14:33:00 +0100 IST: 26.78515625
+    2021-10-26 14:34:00 +0100 IST: 26.64152704258496
+    2021-10-26 14:35:00 +0100 IST: 26.68106989897461
+    2021-10-26 14:36:00 +0100 IST: 26.76746009308496
+    2021-10-26 14:37:00 +0100 IST: 26.78515625
+}
+>>> a[0]
+26.77301344308594
+>>> a[time("2021-10-26T14:34:05+01:00")]
+26.64152704258496
+
+
+
+
+

dict

+

The dict type is a collection of key/value pairs (map).

+
+

Note

+

There are no literals for dict but an empty dict can be created using the newDict function, and +its fields can be set using the bracket operator assignments or various filters such as setFields.

+
+
>>> let d = newDict()
+>>> d
+dict{}
+>>> d["key1"] = 1
+>>> d["key2"] = 2
+>>> d
+dict{
+    key1: 1
+    key2: 2
+}
+>>> d | setFields("key2", 0, "key3", 3)
+dict{
+    key1: 1
+    key2: 0
+    key3: 3
+}
+
+
+
+
+

unknown

+

The unknown type is applied to any value that is not a standard AQL type. Some of the data in CloudVision +can be of a type that does not match any of the native AQL types. There is limited support +to extract and use these values (they can be used in dict keys and values).

+
+

Note

+

There are no literals but some values of that type can be created using the complexKey function. +See sections Complex path elements and complexKey.

+
+
+
+
+

Language keywords

+

Here is a full list of the language keywords in AQL:

+ +
+
+

Language Operators

+

From lowest to highest precedence:

+
    +
  • = (assignment)

  • +
  • ? : (ternary operations)

  • +
  • || (logical OR)

  • +
  • && (logical AND)

  • +
  • != == (equality check operators)

  • +
  • < <= >= > (comparison operators)

  • +
  • + - (addition/concatenation and subtraction)

  • +
  • * / % (multiplication, division, modulo)

  • +
  • | (pipe for filters)

  • +
  • ^ (power)

  • +
  • ! (logical NOT)

  • +
  • [] (access values at a specific index/key/time in timeseries/dicts)

  • +
+
+
+

Comparisons

+

Two values of the same type can be compared.

+

Equality operators (== and !=) work with values of any type, even dict and timeseries (but +both values must be of the same type)

+
>>> true == false
+false
+>>> let s = "myString"
+>>> "myString" == s
+true
+>>> 2 != 3
+true
+
+
+

Values can also be compared using the greater and lower operators (<, <=, >, >=). Both compared +values must have the same type, either str (ASCII order), num, time (before or after), or duration.

+
>>> myVar1
+4
+>>> myVar1 > 4
+false
+>>> myVar1 >= 4
+true
+myVar1 == 4
+true
+>>> let myBooleanVar = myVar1 + 1 <= 5
+>>> myBooleanVar
+true
+>>> "ab" < "ac"
+true
+
+
+
+
+

Operations

+
+

Boolean operations

+

Boolean values can be used with ! (NOT), && (AND), and || (OR) for boolean logic

+
>>>  myBooleanVar
+true
+>>>  myBooleanVar && 1 > 2
+false
+>>> !(myBooleanVar && 1 > 2) && !_ || 1 > 2
+true
+
+
+
+
+

String concatenations

+

Strings can be concatenated with the + operator.

+
>>> "Hello " + "world" + "!"
+Hello world!
+
+
+
+
+

Additions and Subtractions

+

Additions (+) and subtractions (-) can be performed with the following type combinations:

+
    +
  • num + num: returns a num

  • +
  • num - num: returns a num

  • +
  • time - time: returns a duration

  • +
  • time + duration: returns a time

  • +
  • time - duration: returns a time

  • +
+
>>> 2+3.4
+5.4
+>>> let n = now() # now() returns the current time as a `time` value
+>>> n
+2021-10-26 15:19:56.184361 +0100
+>>> let n2 = n - 15m
+>>> n2
+2021-10-26 15:04:56.184361 +0100
+>>> n - n2
+15m0s
+>>> n2 + 15*60s == n
+true
+
+
+
+
+

Multiplications and Divisions

+

Multiplications (*) and divisions (/) can be performed with the following type combinations:

+
    +
  • num * num: returns a num

  • +
  • num / num: returns a num

  • +
  • num * duration: returns a duration

  • +
  • duration / num: returns a duration

  • +
+
>>> 3*3
+9
+>>> 4.4/4
+1.1
+>>> 3*60s
+3m0s
+>>> 3m/180
+1s
+
+
+
+
+

Modulo

+

The modulo (%) operator returns the remainder of a division. It can only be used with two num values.

+
>>> 10 % 3
+1
+
+
+
+
+

Power

+

The power (^) operator returns a to the power of b. It can only be used with two num values.

+
>>> 3^3
+27
+
+
+
+
+
+

Typecasts

+

It is possible to cast values of a certain type to another using the syntax typename(valueToCast). +Here is a typecast table defining which types can be cast to which other types.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

FROM / TO

num

bool

str

time

duration

type

timeseries

dict

unknown

num

YES

YES

YES

YES

YES

YES

NO

NO

NO

bool

YES

YES

YES

NO

NO

YES

NO

NO

NO

str

YES

YES

YES

YES

YES

YES

NO

NO

NO

time

YES

YES

YES

YES

NO

YES

NO

NO

NO

duration

YES

NO

YES

NO

YES

YES

NO

NO

NO

type

NO

NO

YES

NO

NO

YES

NO

NO

NO

timeseries

NO

NO

YES

NO

NO

YES

YES

NO

NO

dict

NO

NO

YES

NO

NO

YES

NO

YES

NO

unknown

NO

NO

YES

NO

NO

YES

NO

NO

NO

+
+

Note

+
    +
  • For all casts between num, duration, and time, the time unit is the nanosecond

  • +
  • Cast from str to num supports float and integer notation but also scientific (1e+2, 15e-3 etc.)

  • +
  • Casts between time and str follow the syntax defined in +RFC 3339.

  • +
+
+
>>> num("12")
+12
+>>> str(11+1) + "a"
+12a
+>>> type("42")
+str
+>>> type(type("42"))
+type
+
+
+

As described in the section about type type, type names can be used as type literals to perform +type-assertions.

+
>>> type(false) == str
+false
+>>> type(false) == bool
+true
+
+
+
+
+

Queries

+

AQL can fetch data from the CloudVision database by using queries. The general syntax is the following:

+
`datasetType/datasetName:/path/to/data`[queryParameter]
+
+
+
+

Dataset

+

The dataset section of the query is split into two parts with a forward slash (/). The first part +is the dataset type (e.g. device, app, config…).

+

The second part is the dataset name.

+

Example:

+
`user/johndoe:/path/to/data`[queryParameter]
+
+
+

If unspecified, the dataset type will default to device:

+
`JPE123456:/path/to/data`[queryParameter]
+
+
+
+
+

Path

+

The path section of the query is the path to the data in the CloudVision database, and each path +element is separated by a forward slash (/).

+
`analytics:/Devices/JPE123456/versioned-data/interfaces/data/Ethernet1/rates`[queryParameter]
+
+
+

A query with a fully specified path like the above will always return a timeseries.

+

The value associated with each specific time in the timeseries is a dict containing all the +key-value pairs updated at that path, at that specific time.

+
+
+

Wildcards

+

If a path element or the dataset name (dataset type can not be wildcarded) is replaced with a simple +star sign (*), called a wildcard, the query fetches the data at all the paths matching this wildcarded path.

+

Example: In the previous section, the query was fetching the interface rates for device “JPE123456”, +and interface “Ethernet1”. This example gets the interface rates for all interfaces of device “JPE123456”.

+
`analytics:/Devices/JPE123456/versioned-data/interfaces/data/*/rates`[queryParameter]
+
+
+

Queries containing a wildcard do not return a timeseries, but a dict. Its keys are the path +element values matching the wildcard (in the example above, the interface names). The dict values +are the timeseries that would have been returned if querying the same path with the wildcard +replaced with each possible key.

+
>>> `analytics:/Devices/JPE123456/versioned-data/interfaces/data/Ethernet1/rates`[0]
+timeseries{
+    start: 2021-10-26 16:12:46.870252166 +0100 IST
+    end: 2021-10-26 16:12:47.674314 +0100 IST
+    2021-10-26 16:12:46.870252166 +0100 IST: dict{
+        inMulticastPkts: 0.5000081856910986
+        inOctets: 61.50100684000513
+        outMulticastPkts: 0
+        outOctets: 0
+    }
+}
+>>> `analytics:/Devices/JPE123456/versioned-data/interfaces/data/*/rates`[0]
+dict{
+    Ethernet1: timeseries{
+        start: 2021-10-26 16:13:16.870363498 +0100 IST
+        end: 2021-10-26 16:13:34.615865 +0100 IST
+        2021-10-26 16:13:16.870363498 +0100 IST: dict{
+            outMulticastPkts: 0
+            outOctets: 0
+        }
+        2021-10-26 16:13:26.870256382 +0100 IST: dict{
+            inMulticastPkts: 0.5000009149562546
+            inOctets: 61.50011253961932
+        }
+    }
+    Ethernet2: timeseries{
+        start: 2021-10-26 16:13:26.870256382 +0100 IST
+        end: 2021-10-26 16:13:34.615865 +0100 IST
+        2021-10-26 16:13:26.870256382 +0100 IST: dict{
+            inMulticastPkts: 0.10000018299125094
+            inOctets: 38.50007045163161
+            inUcastPkts: 0.20000036598250187
+            outMulticastPkts: 0.10000018299125094
+            outOctets: 12.80002342288012
+        }
+    }
+
+
+

A query can also contain multiple wildcards, which will result in several levels of nested dicts, +with timeseries at the bottom level.

+

This example fetches the same data as before, but for all interfaces of all devices, using two +wildcards:

+
>>> `analytics:/Devices/*/versioned-data/interfaces/data/*/rates`[0]
+dict{
+    JPE123456: dict{
+        Ethernet1: timeseries{
+            start: 2021-10-26 16:13:16.870363498 +0100 IST
+            end: 2021-10-26 16:13:34.615865 +0100 IST
+            2021-10-26 16:13:16.870363498 +0100 IST: dict{
+                outMulticastPkts: 0
+                outOctets: 0
+            }
+            2021-10-26 16:13:26.870256382 +0100 IST: dict{
+                inMulticastPkts: 0.5000009149562546
+                inOctets: 61.50011253961932
+            }
+        }
+        Ethernet2: timeseries{
+            start: 2021-10-26 16:13:26.870256382 +0100 IST
+            end: 2021-10-26 16:13:34.615865 +0100 IST
+            2021-10-26 16:13:26.870256382 +0100 IST: dict{
+                inMulticastPkts: 0.10000018299125094
+                inOctets: 38.50007045163161
+                inUcastPkts: 0.20000036598250187
+                outMulticastPkts: 0.10000018299125094
+                outOctets: 12.80002342288012
+            }
+        }
+    }
+    JPE654321: dict{
+        Ethernet1: timeseries{
+            start: 2021-10-26 16:13:16.870363498 +0100 IST
+            end: 2021-10-26 16:13:34.615865 +0100 IST
+            2021-10-26 16:13:16.870363498 +0100 IST: dict{
+                outMulticastPkts: 0
+                outOctets: 0
+            }
+            2021-10-26 16:13:26.870256382 +0100 IST: dict{
+                inMulticastPkts: 0.50000037628384
+                inOctets: 67.638619033792
+            }
+        }
+        Ethernet2: timeseries{
+            start: 2021-10-26 16:13:26.870256382 +0100 IST
+            end: 2021-10-26 16:13:34.615865 +0100 IST
+            2021-10-26 16:13:26.870256382 +0100 IST: dict{
+                inMulticastPkts: 0.10000027274982
+                inOctets: 33.478329283748833
+                inUcastPkts: 0.20000036598250187
+                outMulticastPkts: 0.100000432767384
+                outOctets: 12.828728378483
+            }
+        }
+    }
+
+
+

For a dataset wildcard, the result is built with the same structure. The syntax is as follows:

+
`user/*:/some/path`[queryParameter] # This will get data for all `user` datasets
+`*:/some/path`[queryParameter] # This will get data for all `device` datasets
+
+
+
+
+

Complex path elements

+

Most paths in the database are made of string path elements. In AQL, they are natively handled and +are separated with slashes in queries. However, some paths can contain path elements of different +types, some of which don’t exist in AQL. AQL, however, supports some of them using the curly +brackets syntax.

+
+

Numerical value

+

A numerical literal can be used between the curly brackets, and will produce an int path element if +the literal is an integer literal, and a float path element when it has a decimal part (nil or not).

+
>>> `myDataset:/foo/{12}/bar` # int path element
+>>> `myDataset:/foo/{12.}/bar` # float path element
+>>> `myDataset:/foo/{12.0}/bar` # float path element
+>>> `myDataset:/foo/{12.35}/bar` # float path element
+
+
+
+
+

Boolean value

+

A boolean literal can be used between the curly brackets.

+
>>> `myDataset:/foo/{true}/bar` # bool (true) path element
+>>> `myDataset:/foo/{false}/bar` # bool (false) path element
+>>> `myDataset:/foo/true/bar` # string path element
+>>> `myDataset:/foo/{"true"}/bar` # string path element (identical to the previous one)
+
+
+
+
+

String value

+

A string literal can be used between the curly brackets. This is mostly useful for path elements that +contain a slash

+
>>> `myDataset:/foo/{"my string value"}/bar` # string path element
+>>> `myDataset:/foo/{"my/string/with/slashes"}/bar` # string path element containing slashes
+>>> `myDataset:/foo/my\/string\/with\/slashes/bar` # identical to the previous one
+
+
+
+
+

Map value

+

A map can be input using the JSON syntax (curly brackets and comma-separated colon-linked pairs). +JSON does not know the difference between floats and ints, so a numerical value with a nil decimal +part will be interpreted as an int, and one with a non-nil decimal part will be interpreted as a +float. Can contain nested lists and maps.

+
>>> `myDataset:/foo/{"key": 1.0}/bar` # map("key": int(1)) path element
+>>> `myDataset:/foo/{"key": 1}/bar` # map("key": int(1)) path element
+>>> `myDataset:/foo/{"key": 1.1}/bar` # map("key": float(1.1)) path element
+>>> `myDataset:/foo/{"key": "val", "keyb": true}/bar` # map("key": str("val"), "keyb": bool(true))
+
+
+
+
+

List value

+

A list can be input using the JSON syntax (square brackets and comma-separated values). JSON does +not know the difference between floats and ints, so a numerical value with a nil decimal part will +be interpreted as an int, and one with a non-nil decimal part will be interpreted as a float. Can +contain nested lists and maps

+
>>> `myDataset:/foo/[1.0, 1, 1.1]/bar` # list(int(1), int(1), float(1.1)) path element
+>>> `myDataset:/foo/[true, "str", {"subkey": "subval"}, [1]]/bar` # list(bool(true), str("str"), map("subkey": str("subval")), list([int(1)]))
+
+
+
+
+
+

Query parameter

+

The query parameter is specified within the square brackets attached to the query. It determines +the amount (time range or number of updates) of data to fetch.

+

The parameter must be written as a num or duration literal. It cannot use the value of a variable.

+
+

No parameter

+

If the parameter is not specified, it is equivalent to specifying 0 within the brackets. In that case, +the query will only return the state of data at the current time.

+

This timeseries can contain multiple updates if the keys at this path were last update at different times.

+

In the example below, keys were last updated in 3 different updates, so the timeseries contains 3 updates.

+
>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`
+timeseries {
+    start: 2021-10-26 16:13:26 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:26 +0100 IST: dict{
+        key4: 5
+        key5: 6
+    }
+    2021-10-26 16:13:29 +0100 IST: dict{
+        key3: 2
+    }
+    2021-10-26 16:13:34 +0100 IST: dict{
+        key1: 2
+        key2: 1
+    }
+}
+
+
+
+

Note: Merging the result

+

When getting only the current state (not specifying any parameter), it is common +practice to use the merge function, which will turn a timeseries of dicts into a simple dict, +containing the latest value for each possible key. This allows for direct manipulation of data.

+
+
>>> merge(`analytics:/Devices/JPE12345/path/to/some/interface/data`)
+dict{
+    key1: 2
+    key2: 1
+    key3: 2
+    key4: 5
+    key5: 6
+}
+
+
+
+

Warning

+

Do not confuse the query parameter with the bracket operator that accesses a specific update +in an existing timeseries.

+

In the following example, the first bracket expression is the query parameter, and the second is the +index of the value to get in the resulting timeseries.

+
>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[0][0]
+dict{
+    key4: 5
+    key5: 6
+}
+
+
+

If you want to use the “index-access” bracket operator and not specify a query parameter, you must either +explicitly define the query parameter before, or surround the query with parentheses.

+
>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[0]
+timeseries {
+    start: 2021-10-26 16:13:26 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:26 +0100 IST: dict{
+        key4: 5
+        key5: 6
+    }
+    2021-10-26 16:13:29 +0100 IST: dict{
+        key3: 2
+    }
+    2021-10-26 16:13:34 +0100 IST: dict{
+        key1: 2
+        key2: 1
+    }
+}
+>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`["key4"]
+error: input:1:1: bracket selector of query can only get a num or duration, got str
+>>> (`analytics:/Devices/JPE12345/path/to/some/interface/data`)["key4"]
+error: input:1:16: operator [] applied to timeseries needs a value of type num or time
+>>> (`analytics:/Devices/JPE12345/path/to/some/interface/data`)[0]
+dict{
+    key4: 5
+    key5: 6
+}
+>>> (`analytics:/Devices/JPE12345/path/to/some/interface/data`)[0]["key4"]
+5
+>>> merge(`analytics:/Devices/JPE12345/path/to/some/interface/data`)["key4"]
+5
+
+
+
+
+
+

Number of updates

+

If the square brackets contain a num literal, this num defines what number n of updates to get. +The query will fetch the n latest updates at this path, with each update corresponding to an entry +in the resulting timeseries. However, the length of the timeseries can be superior to n, because +the query also gets the “state” of data before the n updates, i.e. the last update for each key at this +path before the n updates.

+

In the example below, the query requests 3 updates. However, the timeseries returned has a length of 5. +This is because the oldest update of the 3 only updates the value of keys key4 and key5, but not key1, +key2, and key3. Therefore the query also returns the latest update before it for each of these keys. +Here, there are two of these “state” updates: one updates both key1 and key2, and the other updates key3.

+
>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[3]
+timeseries {
+    start: 2021-10-26 16:13:16 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:16 +0100 IST: dict{
+        key1: 1
+        key2: 2
+    }
+    2021-10-26 16:13:23 +0100 IST: dict{
+        key3: 1
+    }
+    2021-10-26 16:13:26 +0100 IST: dict{
+        key4: 5
+        key5: 6
+    }
+    2021-10-26 16:13:29 +0100 IST: dict{
+        key1: 2
+        key3: 1
+    }
+    2021-10-26 16:13:34 +0100 IST: dict{
+        key1: 2
+        key2: 1
+    }
+}
+
+
+

If the oldest of the 3 updates had updated all the keys stored at this path, there would not have been +any “state” update and the length of the timeseries would have been 3:

+
>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[3]
+timeseries {
+    start: 2021-10-26 16:13:26 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:26 +0100 IST: dict{
+        key1: 5
+        key2: 4
+        key3: 3
+        key4: 2
+        key5: 1
+    }
+    2021-10-26 16:13:29 +0100 IST: dict{
+        key1: 2
+        key3: 1
+    }
+    2021-10-26 16:13:34 +0100 IST: dict{
+        key1: 2
+        key2: 1
+    }
+}
+
+
+
+
+

Duration

+

If the square brackets contain a duration literal, this specifies the time range of data returned +by the query.

+

The query will return all the updates that happened at this path during the last d duration, along +with the “state” updates, following the same rules as the number of updates.

+

In the example below, the query fetches the latest 8 seconds of data. In this interval, three updates +happened, the oldest of which only updated key4 and key5, so the returned timeseries also contains +two older updates, which are the latest updates for key1, key2 and key3 before now() - 8s.

+
>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[8s]
+timeseries {
+    start: 2021-10-26 16:13:16 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:16 +0100 IST: dict{
+        key1: 1
+        key2: 2
+    }
+    2021-10-26 16:13:23 +0100 IST: dict{
+        key3: 1
+    }
+    2021-10-26 16:13:26 +0100 IST: dict{
+        key4: 5
+        key5: 6
+    }
+    2021-10-26 16:13:29 +0100 IST: dict{
+        key1: 2
+        key3: 1
+    }
+    2021-10-26 16:13:34 +0100 IST: dict{
+        key1: 2
+        key2: 1
+    }
+}
+
+
+
+
+

Fixed timestamps range

+

It is also possible to use two timestamps separated with a colon (:). This syntax allows querying +data that was written between these timestamps, along with the state data from before the first +timestamp.

+

Like for every query parameter, it is not possible to use a regular variable as one of the timestamps. +They have to be defined directly within the square brackets, or use an input metavariable (defined +outside of the AQL script scope).

+
>>> `analytics:/path/to/data`[time("2022-01-26T16:00:00+00:00"):time("2022-01-26T16:01:00+00:00")]
+timeseries {
+    start: 2022-01-26 16:00:00 +0000 GMT
+    end: 2022-01-26 16:01:00 +0000 GMT
+    2021-10-26 15:59:30 +0100 IST: dict{
+        key1: 1
+    }
+    2021-10-26 16:00:00 +0100 IST: dict{
+        key1: 2
+    }
+    2021-10-26 16:00:30 +0100 IST: dict{
+        key1: 3
+    }
+    2021-10-26 16:01:00 +0100 IST: dict{
+        key1: 4
+    }
+}
+
+
+

Example with input variables:

+
>>> `analytics:/path/to/data`[_startTime:_endTime]
+timeseries {
+    start: 2022-01-26 16:00:00 +0000 GMT
+    end: 2022-01-26 16:01:00 +0000 GMT
+    2021-10-26 15:59:30 +0100 IST: dict{
+        key1: 1
+    }
+    2021-10-26 16:00:00 +0100 IST: dict{
+        key1: 2
+    }
+    2021-10-26 16:00:30 +0100 IST: dict{
+        key1: 3
+    }
+    2021-10-26 16:01:00 +0100 IST: dict{
+        key1: 4
+    }
+}
+
+
+
+
+
+
+

Square bracket operator

+

When applied to a collection (dict or timeseries), the square bracket operator allows access to +a specific value of that collection.

+
+

Timeseries

+

For a timeseries, the type specified within the square brackets can be either a num for access to +a specific numerical index (starts at 0) in the timeseries, or a time, for access to a the value at +a specific time (if there is no exact match, it will return the latest value before the specied time).

+

When accessing a timeseries value using the square bracket operator, the interpreter sets the metavariables +_bracketTime and _bracketIndex to the exact time and index associated with that value.

+

The num index can be negative, in which case it starts from the end of the timeseries (index -1 is the +last update)

+
>>> myTimeseries
+timeseries {
+    start: 2021-10-26 16:13:16 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:16 +0100 IST: "val1"
+    2021-10-26 16:13:23 +0100 IST: "val2"
+    2021-10-26 16:13:26 +0100 IST: "val3"
+    2021-10-26 16:13:29 +0100 IST: "val4"
+    2021-10-26 16:13:34 +0100 IST: "val5"
+}
+>>> myTimeseries[time("2021-10-26T16:13:25+01:00")]
+val2
+>>> _bracketTime
+2021-10-26 16:13:23 +0100 IST
+>>> _bracketIndex
+1
+>>> myTimeseries[-2]
+val4
+>>> _bracketTime
+2021-10-26 16:13:29 +0100 IST
+>>> _bracketIndex
+3
+
+
+
+
+

Dict

+

The square bracket operator allows access to the value associated with a specific key in a dict. +The key can be of any valid key type:

+
    +
  • num

  • +
  • bool

  • +
  • str

  • +
  • any value returned by the complexKey function (even if type is unknown)

  • +
+
>>> let d = newDict() | setFields("key", 1, 2, 3, complexKey("{\"k\": \"v\"}"), 4)
+>>> d
+dict{
+    2: 3
+    key: 1
+    {"k":"v"}: 4
+}
+>>> d[2]
+3
+>>> d["key"]
+1
+>>> d[complexKey("{\"k\": \"v\"}")]
+4
+
+
+

This operator also allows for setting values in the dict.

+
>>> let d = newDict()
+>>> d["key"] = "value"
+>>> d
+dict{key: value}
+
+
+
+
+
+

If/Else

+

AQL also supports if / else conditions. The syntax is as follows:

+
if condition {
+    # statements
+} else {
+    # statements
+}
+
+
+

It is possible to write only the if block and not the else.

+
if condition {
+    # statements
+}
+
+
+

Variables in AQL are not scoped. This means that variables defined within the scope of the if / else +can be accessed from outside.

+
>>> a
+error: input:1:1: undeclared variable: a
+>>> if 5 > 3 {
+...     let a = 1
+... }
+>>> a
+1
+
+
+

The metavariable _ is set even by statements within the scope of an if / else.

+
>>> let a = 6 * 7
+>>> if a == 42 || a == 6 * 9 {
+...     "a is the answer"
+... } else {
+...     "a is not the answer"
+... }
+>>> _
+a is the answer
+
+
+

However, the if / else statement itself does not have a return value, like a variable assignment. +Therefore, this script will not return "a is not the answer" but will have no return value:

+
let a = 5
+if a == 42 || a == 6 * 9 {
+    "a is the answer"
+} else {
+    "a is not the answer"
+}
+
+
+

To return this value at the end of the script, it is possible to just add a statement that simply +returns the _ value. In that case, the script will return "a is not the answer":

+
let a = 5
+if a == 42 || a == 6 * 9 {
+    "a is the answer"
+} else {
+    "a is not the answer"
+}
+_
+
+
+
+
+

Ternary expressions

+

Ternary expressions allow to use conditions directly within an expression. The syntax is similar to +that of the C language.

+
condition ? valueIfConditionIsTrue : valueIfConditionIsFalse
+
+
+

This can be used in any context that manipulates a value.

+
>>> let a = 2
+>>> let b = a < 3 ? "a lower than 3" : "a greater than 3"
+>>> b
+a lower than 3
+
+
+

Ternary expressions can be nested.

+
>>> let a = 2
+>>> let b = a > 0 ? a > 5 ? "big" : "small" : "negative"
+>>> b
+small
+
+
+

Ternary expressions are mostly used within programmatic filters such as map (see section Filters), +because these filters only allow pure expressions, and statements such as if / else or variable +declarations statements cannot be used in their scope.

+
>>> let data = `analytics:/some/data/path`[16s] | field("avg")
+>>> let threshold = 10
+>>> data | map(_value <= threshold ? _value : "forbidden")
+timeseries {
+    start: 2021-10-26 16:13:16 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:16 +0100 IST: 5
+    2021-10-26 16:13:23 +0100 IST: forbidden
+    2021-10-26 16:13:26 +0100 IST: 3
+    2021-10-26 16:13:29 +0100 IST: 10
+    2021-10-26 16:13:34 +0100 IST: forbidden
+}
+
+
+
+
+

Loops

+

AQL supports two kinds of loops: for and while.

+
+

For loop

+

The for loop iterates over an existing collection (dict or timeseries). The syntax is as follows:

+
for k, v in collection {
+    # statements
+}
+
+
+

In the example above, k takes the current key (or timestamp if the collection is a timeseries), +and v its associated value at each iteration. k and v are not predefined names and can be named +any valid variable name by the user:

+
>>> let myDict = newDict() | setFields("k1", 1, "k2", 2)
+>>> let s = ""
+>>> for myKey, myValue in myDict {
+...     let s = s + "{" + str(myKey) + ": " + str(myValue) + "}"
+... }
+>>> s
+{k1: 1}{k2: 2}
+
+
+

It is possible to specify only one variable instead of both key and value. In this case, the variable +will only take the value at each iteration, and the timestamp/key will not be used.

+
>>> let myDict = newDict() | setFields("k1", 1, "k2", 2)
+>>> let i = 0
+>>> for val in myDict {
+...     let i = i + val
+... }
+>>> i
+3
+
+
+
+
+

While loop

+

While loops will iterate as long as the specified condition is true. The syntax is as follows:

+
while condition {
+    # statements
+}
+
+
+

Here is an example that computes the factorial of 6.

+
>>> let fact6 = 1
+>>> let a = 1
+>>> while a <= 6 {
+...     let fact6 = fact6 * a
+...     let a = a + 1
+... }
+>>> fact6
+720
+
+
+
+
+
+

Functions

+

The Standard Library of AQL offers a wide range of functions. Each of them is documented in the +Standard Library page.

+

However, it is not possible to define new functions in AQL.

+

The syntax to call a standard library function is as follows:

+
functionName(argument1)
+functionName(argument1, argument2)
+
+
+

The number of arguments varies depending on the function.

+

Some examples:

+
>>> let data = `analytics:/some/data/path`[10s]
+>>> length(data) # length returns the length of a dict or timeseries
+5
+>>> let avgData = data | field("avg")
+>>> avgData
+timeseries {
+    start: 2021-10-26 16:13:25 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:25 +0100 IST: 5
+    2021-10-26 16:13:27 +0100 IST: 2
+    2021-10-26 16:13:29 +0100 IST: 3
+    2021-10-26 16:13:31 +0100 IST: 10
+    2021-10-26 16:13:33 +0100 IST: 1
+}
+>>> mean(avgData)
+4.2
+
+
+
+
+

Filters

+

Filters are one of the most powerful features in AQL. They allow filtering, formatting, and refining of data +returned by queries very easily and much more efficiently than with loops and manual data manipulation.

+

The AQL Standard Library offers a wide range of filters, designed to adapt to the data structures of +the CloudVision database. Each of them is documented in the Standard Library page.

+

Filters can only be applied to a collection (dict or timeseries), and do not alter the data in the +filtered collection. They return a new collection of the same type, with its content filtered or altered.

+

The syntax is as follows:

+
collection | filterName(argument1, argument2)
+
+
+

The number of arguments varies depending on the filter, and filters can be chained. Some filters, like +fields or setFields for example, take a variable number of arguments.

+
collection | filter1(argument1) | filter2(argument1, argument2) | filter3(argument1)
+
+
+

Some filters, such as map or where take expressions as arguments, and set metavariables that can +be used in these expressions to manipulate the content of the collection. Here are some examples +with a dict but these filters work with timeseries as well. For more examples with either type of +collection, see the detailed documentation for each filter.

+
>>> let d = newDict() | setFields("k1", 1, "k2", 2, "k3", 3)
+>>> d
+dict{
+    k1: 1
+    k2: 2
+    k3: 3
+}
+>>> d | map(_value * 10)
+dict{
+    k1: 10
+    k2: 20
+    k3: 30
+}
+>>> d
+dict{
+    k1: 1
+    k2: 2
+    k3: 3
+}
+>>> d | map(_value * 10) | where(_value <= 20)
+dict{
+    k1: 10
+    k2: 20
+}
+>>> d | map(str(_value * 10) + _key)
+dict{
+    k1: 10k1
+    k2: 20k2
+    k3: 30k3
+}
+
+
+

The expression within a filter can use nested filters.

+
>>> let d = newDict() | setFields("d1", newDict() | setFields("k1", 1), "d2", newDict() | setFields("k1", 2))
+dict{
+        d1: dict{
+                k1: 1
+        }
+        d2: dict{
+                k1: 2
+        }
+}
+>>> d | map(_value * 10)
+error: input:1:4: input:1:15: operator * cannot be used with dict and num
+>>> d | map(_value | map(_value * 10))
+dict{
+        d1: dict{
+                k1: 10
+        }
+        d2: dict{
+                k1: 20
+        }
+}
+
+
+
+
+

Directives

+

Directives allow setting some options before running an AQL script. They must be specified at the beginning +of the script code and will be set for the entire execution.

+

The syntax is as follows:

+
%directiveName = true|false
+
+
+

The only directive currently allowed is includeDecommissionedDevices. It makes device dataset +wildcards include decommissioned devices’ datasets. By default, these datasets are not included.

+
%includeDecommissionedDevices = true
+
+`*:/Sysdb/some/path/to/data`
+
+
+
+
+

Named wildcards and per-value execution

+
+

Warning

+

This section covers features that apply outside of the scope of a single AQL script execution. +Named wildcards are a part of AQL syntax but will modify how the interpreter behaves, by making +it run the script multiple times instead of one, with different input variables at each execution.

+

This execution can then produce multiple outputs.

+
+
+

Default behaviour

+

It is possible to insert a named wildcard into a query with the following syntax: <wildcardName>. +When a named wildcard is set in a query, the interpreter catches it before running the AQL script; +instead of running it once, it runs the script multiple times for each path element matching the named +wildcard.

+

In each run, the value of the path element is also set by the interpreter as a metavariable. +The name of that variable is always prefixed with an underscore, like all metavariables, but you do not +have to specify that underscore in the wildcard name. Therefore, these two syntaxes have the exact same +result, with the path element being stored in variable _device

+
`analytics:/Devices/<device>/interfaces/data`
+
+
+
`analytics:/Devices/<_device>/interfaces/data`
+
+
+

Example:

+

Regular wildcard:

+
let data = `*:/some/path/to/data`[1m]
+data # This contains the data for all datasets (type = dict of timeseries)
+
+
+

Named wildcard:

+
let data = `<d>:/some/path/to/data`[1m]
+let deviceName = _d # deviceName is a single string value, the name of the dataset in the current run
+data # This contains only the data for one dataset (type = timeseries)
+
+
+

For the named wildcard, the AQL script runs multiple times and the interpreter returns the list of all the +outputs. The user only has to manage data for one single dataset within the AQL code.

+

For the regular wildcard, the AQL script runs only once, and contains the data of a datasets in a dict. +The user has to deal with the data of all the datasets manually.

+
+
+

Manual user input

+

When running AQL scripts through CLI, the Service API, or directly using the AQL interpreter library, +it is possible to pass input variables to manually control the multiple runs of named wildcards from +outside the scope of the AQL script.

+

In the AQL library, this is handled through the inputVars parameter, in the Service API, through the +varsets field.

+

In any case, the field is a list that defines the list of times the query will be run. +Each element of the list is a list or map of the variables that the interpreter will set in the environment +before running the AQL script.

+

If these varsets contain values that match the variable name of a named wildcard, the interpreter will +not perform the global GET on this named wildcard and instead run the query one or several times, following +the runs defined in the varset.

+

Example:

+

With these input variable sets:

+
[
+    {"_d": "JPE123456", "_i": "Ethernet5"},
+    {"_d": "JPE123456", "_i": "Ethernet6"},
+    {"_d": "JPE654321", "_i": "Ethernet1"}
+]
+
+
+

The following query will just run three times, twice for device JPE123456 (with interface Ethernet5 +the first time and Ethernet6 the second), and once for device JPE654321, with interface Ethernet1.

+
let interfaceData = `<d>:/Sysdb/hardware/archer/xcvr/status/all/<i>`[10m]
+let deviceAndInterfaceNames = _d + " " + _i # This is just a string containing the device and interface name
+interfaceData # This is a timeseries containing the last 10 mins of data for the current intf and device
+
+
+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/interface_states/admin_state.html b/examples/interface_states/admin_state.html new file mode 100644 index 0000000..8593d6c --- /dev/null +++ b/examples/interface_states/admin_state.html @@ -0,0 +1,128 @@ + + + + + + + Admin state of interfaces per device — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Admin state of interfaces per device

+
let data = `<_device>:/Sysdb/interface/config/eth/phy/slice/*/intfConfig/*`
+let res = newDict()
+for cell in data{
+    for interface, value in cell{
+        let status = merge(value)["adminEnabledStateLocal"]["Name"]
+        # if an interface was never shutdown the state is unknownEnabledState
+        if status == "unknownEnabledState"{
+            let status = "enabled"
+        }
+        res[interface] = newDict() | setFields("Status", status)
+    }
+}
+res
+
+
+Admin state of interfaces per device +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/interface_states/count_if_nz_out_or_in_trfk.html b/examples/interface_states/count_if_nz_out_or_in_trfk.html new file mode 100644 index 0000000..ea1c865 --- /dev/null +++ b/examples/interface_states/count_if_nz_out_or_in_trfk.html @@ -0,0 +1,124 @@ + + + + + + + Count interfaces with non-zero outbound OR inbound traffic (with key existence check) — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • + View page source +
  • +
+
+
+
+
+ +
+

Count interfaces with non-zero outbound OR inbound traffic (with key existence check)

+
# Get the latest rates value for all interfaces in the network using widcards and apply merge on the inner timeseries
+let data = `analytics:/Devices/*/versioned-data/interfaces/data/*/rates`
+
+# keep only entries where the outOctets and inOctets field is present and non-zero
+let latestRates = data | map(_value | where(dictHasKey(merge(_value), "inOctets") && merge(_value)["inOctets"] > 0      \
+                                     || dictHasKey(merge(_value), "outOctets") &&  merge(_value)["outOctets"] > 0))
+
+# count the remaining interfaces (with outbound traffic) for each device and compute the sum to get the number of active interfaces in the entire network
+sum(latestRates | map(length(_value)))
+
+
+Count interfaces with non-zero outbound OR inbound traffic (with key existence check) +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/interface_states/count_if_nz_out_trfk.html b/examples/interface_states/count_if_nz_out_trfk.html new file mode 100644 index 0000000..9d1414d --- /dev/null +++ b/examples/interface_states/count_if_nz_out_trfk.html @@ -0,0 +1,123 @@ + + + + + + + Count interfaces with non-zero outbound traffic — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Count interfaces with non-zero outbound traffic

+
# Get the latest rates value for all interfaces in the network using widcards and apply merge on the inner timeseries
+let data = `analytics:/Devices/*/versioned-data/interfaces/data/*/aggregate/rates/15m` | recmap(2, merge(_value))
+
+# keep only entries where the outOctets field is present and avg is non-zero
+let latestRatesByInterface = data | map(_value | where(dictHasKey(_value, "outOctets") && _value["outOctets"]["avg"] > 0))
+
+#count the remaining interfaces (with outbound traffic) for each device and compute the sum to get the number of active interfaces in the entire network
+sum(latestRatesByInterface | map(length(_value)))
+
+
+Count interfaces with non-zero outbound traffic +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/interface_states/if_info_filter_port_desc.html b/examples/interface_states/if_info_filter_port_desc.html new file mode 100644 index 0000000..f53b443 --- /dev/null +++ b/examples/interface_states/if_info_filter_port_desc.html @@ -0,0 +1,181 @@ + + + + + + + Interface Information wtih a filter on Port Description — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • + View page source +
  • +
+
+
+
+
+ +
+

Interface Information wtih a filter on Port Description

+
# Requires a variable input named PortDescription
+
+# Get the interface names and descriptions
+let intfConfig = `*:/Sysdb/interface/config/eth/phy/slice/1/intfConfig/*` | recmap(2, merge(_value))
+let i = 0
+let result = newDict()
+for deviceKey, deviceVal in intfConfig {
+    for interfaceKey, interfaceVal in deviceVal {
+        if reMatch(interfaceVal["description"], _PortDescription) {
+            result[i] = newDict()
+            result[i]["Switch"] = deviceKey
+            result[i]["Interface"] = interfaceKey
+            result[i]["Port Description"] = interfaceVal["description"]
+            let i = i + 1
+        }
+    }
+}
+
+# Get the LLDP Neighbour Information
+let lldpPeers = `*:/Sysdb/l2discovery/lldp/status/local/1/portStatus/*/remoteSystem/*` | recmap(3, merge(_value))
+for deviceKey, deviceVal in result {
+    for lldpKey, lldpVal in lldpPeers {
+        if lldpKey == deviceVal["Switch"] {
+            for interfaceKey, interfaceVal in lldpVal {
+                if interfaceKey == deviceVal["Interface"] {
+                    let values = interfaceVal[dictKeys(interfaceVal)[0]]
+                    result[deviceKey]["Remote LLDP Hostname"] = values["sysName"]["value"]["value"]
+                    result[deviceKey]["Remote LLDP PortID"] = values["msap"]["portIdentifier"]["portId"]
+                    result[deviceKey]["Chassis Identifier"] = values["msap"]["chassisIdentifier"]["chassisId"]
+                }
+            }
+        }
+    }
+}
+
+# Get the L2 Interface Information (VLAN Info, etc)
+let switchIntfConfig = `*:/Sysdb/bridging/switchIntfConfig/switchIntfConfig/*` | recmap(2, merge(_value))
+for deviceKey, deviceVal in result {
+    for switchKey, switchVal in switchIntfConfig {
+        if switchKey == deviceVal["Switch"] {
+            for interfaceKey, interfaceVal in switchVal {
+                if interfaceKey == deviceVal["Interface"] {
+                    result[deviceKey]["Switchport Mode"] = interfaceVal["switchportMode"]["Name"]
+                    result[deviceKey]["Access VLAN"] = interfaceVal["accessVlan"]["value"]
+                    result[deviceKey]["Trunked VLANs"] = interfaceVal["trunkAllowedVlans"]
+                }
+            }
+        }
+    }
+}
+
+# Get the Interface Status Info
+let intfStatus = `*:/Sysdb/interface/status/eth/phy/slice/1/intfStatus/*` | recmap(2, merge(_value))
+for deviceKey, deviceVal in result {
+    for switchKey, switchVal in intfStatus {
+        if switchKey == deviceVal["Switch"] {
+            for intfKey, intfVal in switchVal {
+                if intfKey == deviceVal["Interface"] {
+                    result[deviceKey]["Status"] = intfVal["operStatus"]["Name"]
+                    result[deviceKey]["Speed"] = intfVal["speedEnum"]["Name"]
+                }
+            }
+        }
+    }
+}
+result
+
+
+Port Utilization +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/interface_states/index.html b/examples/interface_states/index.html new file mode 100644 index 0000000..f75d7f0 --- /dev/null +++ b/examples/interface_states/index.html @@ -0,0 +1,361 @@ + + + + + + + Interface States Examples — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Interface States Examples

+
+

Admin state of interfaces per device

+
let data = `<_device>:/Sysdb/interface/config/eth/phy/slice/*/intfConfig/*`
+let res = newDict()
+for cell in data{
+    for interface, value in cell{
+        let status = merge(value)["adminEnabledStateLocal"]["Name"]
+        # if an interface was never shutdown the state is unknownEnabledState
+        if status == "unknownEnabledState"{
+            let status = "enabled"
+        }
+        res[interface] = newDict() | setFields("Status", status)
+    }
+}
+res
+
+
+Admin state of interfaces per device +

Download the Dashboard JSON here

+
+
+

Count interfaces with non-zero outbound traffic

+
# Get the latest rates value for all interfaces in the network using widcards and apply merge on the inner timeseries
+let data = `analytics:/Devices/*/versioned-data/interfaces/data/*/aggregate/rates/15m` | recmap(2, merge(_value))
+
+# keep only entries where the outOctets field is present and avg is non-zero
+let latestRatesByInterface = data | map(_value | where(dictHasKey(_value, "outOctets") && _value["outOctets"]["avg"] > 0))
+
+#count the remaining interfaces (with outbound traffic) for each device and compute the sum to get the number of active interfaces in the entire network
+sum(latestRatesByInterface | map(length(_value)))
+
+
+Count interfaces with non-zero outbound traffic +

Download the Dashboard JSON here

+
+
+

Count interfaces with non-zero outbound OR inbound traffic (with key existence check)

+
# Get the latest rates value for all interfaces in the network using widcards and apply merge on the inner timeseries
+let data = `analytics:/Devices/*/versioned-data/interfaces/data/*/rates`
+
+# keep only entries where the outOctets and inOctets field is present and non-zero
+let latestRates = data | map(_value | where(dictHasKey(merge(_value), "inOctets") && merge(_value)["inOctets"] > 0      \
+                                     || dictHasKey(merge(_value), "outOctets") &&  merge(_value)["outOctets"] > 0))
+
+# count the remaining interfaces (with outbound traffic) for each device and compute the sum to get the number of active interfaces in the entire network
+sum(latestRates | map(length(_value)))
+
+
+Count interfaces with non-zero outbound OR inbound traffic (with key existence check) +

Download the Dashboard JSON here

+
+
+

Interface Information wtih a filter on Port Description

+
# Requires a variable input named PortDescription
+
+# Get the interface names and descriptions
+let intfConfig = `*:/Sysdb/interface/config/eth/phy/slice/1/intfConfig/*` | recmap(2, merge(_value))
+let i = 0
+let result = newDict()
+for deviceKey, deviceVal in intfConfig {
+    for interfaceKey, interfaceVal in deviceVal {
+        if reMatch(interfaceVal["description"], _PortDescription) {
+            result[i] = newDict()
+            result[i]["Switch"] = deviceKey
+            result[i]["Interface"] = interfaceKey
+            result[i]["Port Description"] = interfaceVal["description"]
+            let i = i + 1
+        }
+    }
+}
+
+# Get the LLDP Neighbour Information
+let lldpPeers = `*:/Sysdb/l2discovery/lldp/status/local/1/portStatus/*/remoteSystem/*` | recmap(3, merge(_value))
+for deviceKey, deviceVal in result {
+    for lldpKey, lldpVal in lldpPeers {
+        if lldpKey == deviceVal["Switch"] {
+            for interfaceKey, interfaceVal in lldpVal {
+                if interfaceKey == deviceVal["Interface"] {
+                    let values = interfaceVal[dictKeys(interfaceVal)[0]]
+                    result[deviceKey]["Remote LLDP Hostname"] = values["sysName"]["value"]["value"]
+                    result[deviceKey]["Remote LLDP PortID"] = values["msap"]["portIdentifier"]["portId"]
+                    result[deviceKey]["Chassis Identifier"] = values["msap"]["chassisIdentifier"]["chassisId"]
+                }
+            }
+        }
+    }
+}
+
+# Get the L2 Interface Information (VLAN Info, etc)
+let switchIntfConfig = `*:/Sysdb/bridging/switchIntfConfig/switchIntfConfig/*` | recmap(2, merge(_value))
+for deviceKey, deviceVal in result {
+    for switchKey, switchVal in switchIntfConfig {
+        if switchKey == deviceVal["Switch"] {
+            for interfaceKey, interfaceVal in switchVal {
+                if interfaceKey == deviceVal["Interface"] {
+                    result[deviceKey]["Switchport Mode"] = interfaceVal["switchportMode"]["Name"]
+                    result[deviceKey]["Access VLAN"] = interfaceVal["accessVlan"]["value"]
+                    result[deviceKey]["Trunked VLANs"] = interfaceVal["trunkAllowedVlans"]
+                }
+            }
+        }
+    }
+}
+
+# Get the Interface Status Info
+let intfStatus = `*:/Sysdb/interface/status/eth/phy/slice/1/intfStatus/*` | recmap(2, merge(_value))
+for deviceKey, deviceVal in result {
+    for switchKey, switchVal in intfStatus {
+        if switchKey == deviceVal["Switch"] {
+            for intfKey, intfVal in switchVal {
+                if intfKey == deviceVal["Interface"] {
+                    result[deviceKey]["Status"] = intfVal["operStatus"]["Name"]
+                    result[deviceKey]["Speed"] = intfVal["speedEnum"]["Name"]
+                }
+            }
+        }
+    }
+}
+result
+
+
+Port Utilization +

Download the Dashboard JSON here

+
+
+

LANZ queue Size information filtering the null-values

+
let intervalToMonitor = "10s" # Can be "1m", "10s" or "1ms"
+let queueToMonitor = "queueSize" # Can be "queueSize" (to monitor queue size), "qDropCount" (to monitor actual drops) or "txLatency" (to monitor Tx latency)
+
+# Section to get a Serial-number to hostname dict (SNToHostnameDict)
+let SNToHostnameDict = newDict()
+let dataDeviceAnalytics = `analytics:/Devices/*/versioned-data/Device`
+for device, deviceData in dataDeviceAnalytics {
+   let mergedDeviceData = merge(deviceData)
+   SNToHostnameDict[device] = mergedDeviceData["hostname"]
+}
+
+# let data = `analytics:/Devices/*/versioned-data/interfaces/data/*/lanz/aggregate-congestion/*`[_timeWindowStart:_timeWindowEnd]
+let data = `analytics:/Devices/*/versioned-data/interfaces/data/*/lanz/aggregate-congestion/*`[7*24h]
+let result = newDict()
+for deviceSN, deviceData in data {
+    for interface, interfaceData in deviceData {
+        for interval, aggData in interfaceData {
+            let empty = true
+            for timestamp, timeseriesData in aggData {
+                if (dictHasKey(timeseriesData, queueToMonitor) && timeseriesData[queueToMonitor]["avg"] > 0 && interval == intervalToMonitor && strHasPrefix(interface, "Ethernet")) {
+                    let empty = false
+                }
+            }
+            if (!empty) {
+                let deviceHostname = SNToHostnameDict[deviceSN]
+                result[interface + " on " + deviceHostname + " (" + interval + ")"] = aggData | map(_value[queueToMonitor]["avg"])
+            }
+
+        }
+    }
+}
+result
+
+
+LANZ queue Size information filtering the null-values +

Download the Dashboard JSON here

+
+
+

Port Utilization

+
# Get devices with tag label "switch_role" and default value of "hdl".
+# To choose another tag value, select from the input's dropdown.
+
+# Load all interface configurations
+let devs = merge(`analytics:/tags/labels/devices/switch_role/value/<_switch_role>/elements`)
+let devices = newDict()
+
+for deviceIDstr, val in devs {
+    let devId = strSplit(str(deviceIDstr), ":")[1]
+    let devId = strReplace(devId,"\"", "")
+    let devId = strReplace(devId, "}", "")
+    devices[devId] = val
+}
+
+let data = `*:/Sysdb/interface/config/eth/phy/slice/1/intfConfig/*` | recmap(2,merge(_value) | fields("description")) | map(_value | where(strContains(_key, "Ethernet")))| where( dictHasKey(devices, _key))
+let result = `*:/Sysdb/interface/config/eth/phy/slice/1/intfConfig/*` | recmap(2,merge(_value) | fields("description")) | map(_value | where(strContains(_key, "Ethernet")))| where( dictHasKey(devices, _key))
+
+for deviceKey, deviceValue in data {
+    let nbPorts = 0
+    let nbUnusedPorts = 0
+    for portKey, portValue in deviceValue {
+       if (portValue["description"]=="UNUSED"){
+        let nbUnusedPorts = nbUnusedPorts+1
+       }
+        let nbPorts = nbPorts+1
+        result[deviceKey]["Number of Unused Ports"] = nbUnusedPorts
+        result[deviceKey]["Number of Ports"] = nbPorts
+        if (nbPorts != 0){
+            result[deviceKey]["Port free %"] = nbUnusedPorts / nbPorts * 100
+        } else {
+            result[deviceKey]["Port free %"] = "N/A"
+        }
+    }
+}
+result | map(_value | fields("Number of Unused Ports","Number of Ports","Port free %"))
+
+
+Port Utilization +

Download the Dashboard JSON here

+
+
+

Interface counters sum per interface list per device

+
+

Note

+

Note that aggregate() function requires AQL revision 4+ to work.

+
+
# Get the 1 minute aggregate rate counters for the selected device
+let data = `analytics:/Devices/<_device>/versioned-data/interfaces/data/*/aggregate/rates/1m`[_timeWindowStart:_timeWindowEnd]
+
+# Filter out the rate counters to only contain the inOctets and outOctets key in two separate objects
+let intfRatesInFiltered = data | map(_value | field("inOctets") | field("avg")) | where(dictHasKey(_interfaces, _key))
+let intfRatesOutFiltered = data | map(_value | field("outOctets") | field("avg")) | where(dictHasKey(_interfaces, _key))
+
+# Sum up the aggregates
+let sumInOctets = aggregate(intfRatesInFiltered, "sum")
+let sumOutOctets = aggregate(intfRatesOutFiltered, "sum")
+
+# Add the result into a dictionary of timeseries for horizon graphs
+# and divide the sum by 125000 to show the value in Mbps
+newDict() | setFields(_device + " inOctets rates", sumInOctets/125000, _device + " outOctets rates", sumOutOctets/125000)
+
+
+Interface counters per interface list per device +

Download the Dashboard JSON here

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/interface_states/intf_counter_rate_sum_per_dev.html b/examples/interface_states/intf_counter_rate_sum_per_dev.html new file mode 100644 index 0000000..9330b0c --- /dev/null +++ b/examples/interface_states/intf_counter_rate_sum_per_dev.html @@ -0,0 +1,133 @@ + + + + + + + Interface counters sum per interface list per device — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Interface counters sum per interface list per device

+
+

Note

+

Note that aggregate() function requires AQL revision 4+ to work.

+
+
# Get the 1 minute aggregate rate counters for the selected device
+let data = `analytics:/Devices/<_device>/versioned-data/interfaces/data/*/aggregate/rates/1m`[_timeWindowStart:_timeWindowEnd]
+
+# Filter out the rate counters to only contain the inOctets and outOctets key in two separate objects
+let intfRatesInFiltered = data | map(_value | field("inOctets") | field("avg")) | where(dictHasKey(_interfaces, _key))
+let intfRatesOutFiltered = data | map(_value | field("outOctets") | field("avg")) | where(dictHasKey(_interfaces, _key))
+
+# Sum up the aggregates
+let sumInOctets = aggregate(intfRatesInFiltered, "sum")
+let sumOutOctets = aggregate(intfRatesOutFiltered, "sum")
+
+# Add the result into a dictionary of timeseries for horizon graphs
+# and divide the sum by 125000 to show the value in Mbps
+newDict() | setFields(_device + " inOctets rates", sumInOctets/125000, _device + " outOctets rates", sumOutOctets/125000)
+
+
+Interface counters per interface list per device +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/interface_states/lanz_queue_size.html b/examples/interface_states/lanz_queue_size.html new file mode 100644 index 0000000..15b3fed --- /dev/null +++ b/examples/interface_states/lanz_queue_size.html @@ -0,0 +1,147 @@ + + + + + + + LANZ queue Size information filtering the null-values — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

LANZ queue Size information filtering the null-values

+
let intervalToMonitor = "10s" # Can be "1m", "10s" or "1ms"
+let queueToMonitor = "queueSize" # Can be "queueSize" (to monitor queue size), "qDropCount" (to monitor actual drops) or "txLatency" (to monitor Tx latency)
+
+# Section to get a Serial-number to hostname dict (SNToHostnameDict)
+let SNToHostnameDict = newDict()
+let dataDeviceAnalytics = `analytics:/Devices/*/versioned-data/Device`
+for device, deviceData in dataDeviceAnalytics {
+   let mergedDeviceData = merge(deviceData)
+   SNToHostnameDict[device] = mergedDeviceData["hostname"]
+}
+
+# let data = `analytics:/Devices/*/versioned-data/interfaces/data/*/lanz/aggregate-congestion/*`[_timeWindowStart:_timeWindowEnd]
+let data = `analytics:/Devices/*/versioned-data/interfaces/data/*/lanz/aggregate-congestion/*`[7*24h]
+let result = newDict()
+for deviceSN, deviceData in data {
+    for interface, interfaceData in deviceData {
+        for interval, aggData in interfaceData {
+            let empty = true
+            for timestamp, timeseriesData in aggData {
+                if (dictHasKey(timeseriesData, queueToMonitor) && timeseriesData[queueToMonitor]["avg"] > 0 && interval == intervalToMonitor && strHasPrefix(interface, "Ethernet")) {
+                    let empty = false
+                }
+            }
+            if (!empty) {
+                let deviceHostname = SNToHostnameDict[deviceSN]
+                result[interface + " on " + deviceHostname + " (" + interval + ")"] = aggData | map(_value[queueToMonitor]["avg"])
+            }
+
+        }
+    }
+}
+result
+
+
+LANZ queue Size information filtering the null-values +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/interface_states/port_utilization.html b/examples/interface_states/port_utilization.html new file mode 100644 index 0000000..ab15b72 --- /dev/null +++ b/examples/interface_states/port_utilization.html @@ -0,0 +1,150 @@ + + + + + + + Port Utilization — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Port Utilization

+
# Get devices with tag label "switch_role" and default value of "hdl".
+# To choose another tag value, select from the input's dropdown.
+
+# Load all interface configurations
+let devs = merge(`analytics:/tags/labels/devices/switch_role/value/<_switch_role>/elements`)
+let devices = newDict()
+
+for deviceIDstr, val in devs {
+    let devId = strSplit(str(deviceIDstr), ":")[1]
+    let devId = strReplace(devId,"\"", "")
+    let devId = strReplace(devId, "}", "")
+    devices[devId] = val
+}
+
+let data = `*:/Sysdb/interface/config/eth/phy/slice/1/intfConfig/*` | recmap(2,merge(_value) | fields("description")) | map(_value | where(strContains(_key, "Ethernet")))| where( dictHasKey(devices, _key))
+let result = `*:/Sysdb/interface/config/eth/phy/slice/1/intfConfig/*` | recmap(2,merge(_value) | fields("description")) | map(_value | where(strContains(_key, "Ethernet")))| where( dictHasKey(devices, _key))
+
+for deviceKey, deviceValue in data {
+    let nbPorts = 0
+    let nbUnusedPorts = 0
+    for portKey, portValue in deviceValue {
+       if (portValue["description"]=="UNUSED"){
+        let nbUnusedPorts = nbUnusedPorts+1
+       }
+        let nbPorts = nbPorts+1
+        result[deviceKey]["Number of Unused Ports"] = nbUnusedPorts
+        result[deviceKey]["Number of Ports"] = nbPorts
+        if (nbPorts != 0){
+            result[deviceKey]["Port free %"] = nbUnusedPorts / nbPorts * 100
+        } else {
+            result[deviceKey]["Port free %"] = "N/A"
+        }
+    }
+}
+result | map(_value | fields("Number of Unused Ports","Number of Ports","Port free %"))
+
+
+Port Utilization +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/layer2_and_layer3/arp_entries.html b/examples/layer2_and_layer3/arp_entries.html new file mode 100644 index 0000000..5c403fa --- /dev/null +++ b/examples/layer2_and_layer3/arp_entries.html @@ -0,0 +1,117 @@ + + + + + + + Number of ARP entries across all devices — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Number of ARP entries across all devices

+
let data = `*:/Smash/arp/status/_counts`
+sum(data | map(merge(_value)) | where(dictHasKey(_value, "arpEntry")) | map(_value["arpEntry"]))
+
+
+Number of ARP entries across all devices +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/layer2_and_layer3/bgp_states.html b/examples/layer2_and_layer3/bgp_states.html new file mode 100644 index 0000000..bd7ce53 --- /dev/null +++ b/examples/layer2_and_layer3/bgp_states.html @@ -0,0 +1,205 @@ + + + + + + + BGP States — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

BGP States

+
+

BGP Session Status

+
let neighbors = `analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`
+
+# Dict to store the states and counts
+let res = newDict()
+# Loop over each device
+for device, deviceSessions in neighbors{
+    # Loop over each session on each device
+    for ip, sessionData in deviceSessions{
+        let data = merge(sessionData)
+        # Have we used this status yet?
+        let status = data["bgpState"]["Name"]
+        if !dictHasKey(res, status) {
+            # If not lets set it use count to zero
+            res[status] = 0
+        }
+        # Add one to the total times this status is used
+        res[status] = res[status] + 1
+    }
+}
+res
+
+
+
+
+

BGP Session Details in the Default VRF for all Devices

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+
+if str(_POD_NAME) == "" {
+    let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`
+} else {
+    let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+
+# This is the table
+let res = newDict()
+let id = 0
+# Lets loop over every device
+for device, deviceSessions in bgpNeighbors{
+    # And each session on the devices
+    for ip, sessionData in deviceSessions{
+        let data = merge(sessionData)
+        # Add one to the ID
+        let id = id + 1
+        res[id] = newDict()
+        # This is where we add the various columns
+        res[id]["0. Device"] = device
+        res[id]["1. Status"] = data["bgpState"]["Name"]
+        res[id]["2. Peering Address"] = data["bgpPeerLocalAddr"]
+        res[id]["3. Neighbor Address"] = data["key"]
+        res[id]["4. Neighbor AS"] = data["bgpPeerAs"]["value"]
+    }
+}
+res
+
+
+
+
+

BGP Sessions that are Not Established

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+
+if str(_POD_NAME) == "" {
+    let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`
+} else {
+    let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+
+# This is the table
+let res = newDict()
+let id = 0
+# Lets loop over every device
+for device, deviceSessions in bgpNeighbors{
+    # And each session on the devices
+    for ip, sessionData in deviceSessions{
+        let data = merge(sessionData)
+        # Add one to the ID
+        let id = id + 1
+        res[id] = newDict()
+        # This is where we add the various columns
+        res[id]["0. Device"] = device
+        res[id]["1. Status"] = data["bgpState"]["Name"]
+        res[id]["2. Peering Address"] = data["bgpPeerLocalAddr"]
+        res[id]["3. Neighbor Address"] = data["key"]
+        res[id]["4. Neighbor AS"] = data["bgpPeerAs"]["value"]
+    }
+}
+
+res | where(_value["1. Status"] != "Established")
+
+
+BGP Sessions that are Not Established +

Download the Dashboard JSON here

+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/layer2_and_layer3/capacity_planning.html b/examples/layer2_and_layer3/capacity_planning.html new file mode 100644 index 0000000..762af08 --- /dev/null +++ b/examples/layer2_and_layer3/capacity_planning.html @@ -0,0 +1,229 @@ + + + + + + + Capacity Planning Routing and Switching — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Capacity Planning Routing and Switching

+
+

High Density Leaf Switches Numbers (7050X3) Tagged with HDL Label

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/HDL/elements`)
+
+if str(_POD_NAME) == "" {
+    let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(length(_value) == 0 ? 0 : _value)
+    let arpCount = `*:/Smash/arp/status/_counts`  | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+} else {
+    let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(length(_value) == 0 ? 0 : _value)
+    let arpCount = `*:/Smash/arp/status/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+
+let smash = macStatusCount | map(merge(_value) | fields("smashFdbStatus"))
+let nbvlan = macConfigCount | map(merge(_value) | fields("vlanConfig"))
+let nbVRF = vrfCount | map(length(merge(_value)))
+let nbARP = arpCount | map(merge(_value))
+
+for deviceKey, deviceValue in nbVRF {
+    let nbr = newDict()
+    nbr["VRF number"]=deviceValue
+    nbVRF[deviceKey]= nbr
+}
+
+for devicekey, devicevalue in smash {
+    smash[devicekey]["VLAN count"]=nbvlan[devicekey]["vlanConfig"]
+    smash[devicekey]["VRF count"]=nbVRF[devicekey]["VRF number"]
+    smash[devicekey]["ARP count"]=nbARP[devicekey]["arpEntry"]
+    smash[devicekey]["ND count"]=nbARP[devicekey]["neighborEntry"]
+}
+
+smash | map(_value | renameFields("smashFdbStatus","MAC count"))
+
+
+
+
+

Low Density Leaf Switches Numbers (7020R) Tagged with the LDL Label

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/LDL/elements`)
+
+if str(_POD_NAME) == "" {
+    let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(length(_value) == 0 ? 0 : _value)
+    let arpCount = `*:/Smash/arp/status/_counts`  | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+} else {
+    let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(length(_value) == 0 ? 0 : _value)
+    let arpCount = `*:/Smash/arp/status/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+
+let smash = macStatusCount | map(merge(_value) | fields("smashFdbStatus"))
+let nbvlan = macConfigCount | map(merge(_value) | fields("vlanConfig"))
+let nbVRF = vrfCount | map(length(merge(_value)))
+let nbARP = arpCount | map(merge(_value))
+
+for deviceKey, deviceValue in nbVRF {
+    let nbr = newDict()
+    nbr["VRF number"] = deviceValue
+    nbVRF[deviceKey] = nbr
+}
+
+for devicekey, devicevalue in smash {
+    smash[devicekey]["VLAN count"] = nbvlan[devicekey]["vlanConfig"]
+    smash[devicekey]["VRF count"] = dictHasKey(nbVRF, devicekey)? nbVRF[devicekey]["VRF number"] : 0
+    smash[devicekey]["ARP count"] = nbARP[devicekey]["arpEntry"]
+    smash[devicekey]["ND count"] = nbARP[devicekey]["neighborEntry"]
+}
+
+smash | map(_value | renameFields("smashFdbStatus","MAC count"))
+
+
+
+
+

EVPN Gateways Numbers (7280R2)

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/EGW/elements`)
+
+if str(_POD_NAME) == "" {
+    let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(length(_value) == 0 ? 0 : _value)
+    let arpCount = `*:/Smash/arp/status/_counts`  | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+} else {
+    let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(length(_value) == 0 ? 0 : _value)
+    let arpCount = `*:/Smash/arp/status/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+
+let smash = macStatusCount | map(merge(_value) | fields("smashFdbStatus"))
+let nbvlan = macConfigCount | map(merge(_value) | fields("vlanConfig"))
+let nbVRF = vrfCount | map(length(merge(_value)))
+let nbARP = arpCount | map(merge(_value))
+
+for deviceKey, deviceValue in nbVRF {
+    let nbr = newDict()
+    nbr["VRF number"]=deviceValue
+    nbVRF[deviceKey]= nbr
+}
+
+for devicekey, devicevalue in smash {
+    smash[devicekey]["VLAN count"]=nbvlan[devicekey]["vlanConfig"]
+    smash[devicekey]["VRF count"]=nbVRF[devicekey]["VRF number"]
+    smash[devicekey]["ARP count"]=nbARP[devicekey]["arpEntry"]
+    smash[devicekey]["ND count"]=nbARP[devicekey]["neighborEntry"]
+}
+
+smash | map(_value | renameFields("smashFdbStatus","MAC count"))
+
+
+

Download the Dashboard JSON here

+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/layer2_and_layer3/igmp_snooping.html b/examples/layer2_and_layer3/igmp_snooping.html new file mode 100644 index 0000000..8d1b7a9 --- /dev/null +++ b/examples/layer2_and_layer3/igmp_snooping.html @@ -0,0 +1,140 @@ + + + + + + + IGMP Snooping Table — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

IGMP Snooping Table

+
+

Note

+

IGMP Snooping states are not streamed by default and the “/Sysdb/bridging/igmpsnooping” needs to be added to the TerminAttr include list using the tastreaming.TerminattrStreaming service API. +Examples can be found on Examples

+
+
let igmpSnooping = `<_device>:/Sysdb/bridging/igmpsnooping/forwarding/status/vlanStatus/*/ethGroup/*/intf`
+let filteredIgmpSnooping = igmpSnooping | recmap(2, merge(_value))
+let result = newDict()
+for vlanKey, macAddrIntf in filteredIgmpSnooping {
+    for macAddr, intfState in macAddrIntf {
+        # Create a list of interfaces a vlan is mapped to
+        let interfaceList = ""
+        if length(intfState) > 1{
+            for interface, intfBool in intfState {
+                let interfaceList = interfaceList + str(interface) + ", "
+            }
+        } else {
+            # if there is only 1 interface no need to add a comma
+            let interfaceList = str(dictKeys(intfState)[0])
+        }
+        let vlan = reFindCaptures(str(vlanKey), "{\"value\":(\d+)}")[0][1]
+        result[macAddr] = newDict() | setFields("VLAN", vlan, "Members", interfaceList)
+    }
+}
+result
+
+
+IGMP Snooping Table +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/layer2_and_layer3/index.html b/examples/layer2_and_layer3/index.html new file mode 100644 index 0000000..89d23bf --- /dev/null +++ b/examples/layer2_and_layer3/index.html @@ -0,0 +1,606 @@ + + + + + + + Layer 2 and Layer 3 examples — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Layer 2 and Layer 3 examples

+
+

Number of ARP entries across all devices

+
let data = `*:/Smash/arp/status/_counts`
+sum(data | map(merge(_value)) | where(dictHasKey(_value, "arpEntry")) | map(_value["arpEntry"]))
+
+
+Number of ARP entries across all devices +

Download the Dashboard JSON here

+
+
+

BGP States

+
+

BGP Session Status

+
let neighbors = `analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`
+
+# Dict to store the states and counts
+let res = newDict()
+# Loop over each device
+for device, deviceSessions in neighbors{
+    # Loop over each session on each device
+    for ip, sessionData in deviceSessions{
+        let data = merge(sessionData)
+        # Have we used this status yet?
+        let status = data["bgpState"]["Name"]
+        if !dictHasKey(res, status) {
+            # If not lets set it use count to zero
+            res[status] = 0
+        }
+        # Add one to the total times this status is used
+        res[status] = res[status] + 1
+    }
+}
+res
+
+
+
+
+

BGP Session Details in the Default VRF for all Devices

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+
+if str(_POD_NAME) == "" {
+    let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`
+} else {
+    let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+
+# This is the table
+let res = newDict()
+let id = 0
+# Lets loop over every device
+for device, deviceSessions in bgpNeighbors{
+    # And each session on the devices
+    for ip, sessionData in deviceSessions{
+        let data = merge(sessionData)
+        # Add one to the ID
+        let id = id + 1
+        res[id] = newDict()
+        # This is where we add the various columns
+        res[id]["0. Device"] = device
+        res[id]["1. Status"] = data["bgpState"]["Name"]
+        res[id]["2. Peering Address"] = data["bgpPeerLocalAddr"]
+        res[id]["3. Neighbor Address"] = data["key"]
+        res[id]["4. Neighbor AS"] = data["bgpPeerAs"]["value"]
+    }
+}
+res
+
+
+
+
+

BGP Sessions that are Not Established

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+
+if str(_POD_NAME) == "" {
+    let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`
+} else {
+    let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+
+# This is the table
+let res = newDict()
+let id = 0
+# Lets loop over every device
+for device, deviceSessions in bgpNeighbors{
+    # And each session on the devices
+    for ip, sessionData in deviceSessions{
+        let data = merge(sessionData)
+        # Add one to the ID
+        let id = id + 1
+        res[id] = newDict()
+        # This is where we add the various columns
+        res[id]["0. Device"] = device
+        res[id]["1. Status"] = data["bgpState"]["Name"]
+        res[id]["2. Peering Address"] = data["bgpPeerLocalAddr"]
+        res[id]["3. Neighbor Address"] = data["key"]
+        res[id]["4. Neighbor AS"] = data["bgpPeerAs"]["value"]
+    }
+}
+
+res | where(_value["1. Status"] != "Established")
+
+
+BGP Sessions that are Not Established +

Download the Dashboard JSON here

+
+
+
+

TAC Webinar03 2023 - BGP States

+
+

BGP Summary

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let bgpPeerInfoStatusEntry = `*:/Sysdb/cell/1/routing/bgp/export/vrfBgpPeerInfoStatusEntryTable/default/bgpPeerInfoStatusEntry/*`
+if str(_POD_NAME) == "" {
+    let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`
+} else {
+    let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+let bgpPeerStatisticsEntry = `*:/Smash/routing/bgp/bgpPeerInfoStatus/default/bgpPeerStatisticsEntry`
+let afis = newDict()
+let afi_name = newDict()
+let bgpPeerAfiSafiActive = newDict() | setFields("l2vpnEvpn", 3, "ipv4Unicast", 1)
+let bgpPeerAfiSafiActiveName = newDict() | setFields("l2vpnEvpn","L2VPN EVPN", "ipv4Unicast", "IPv4 Unicast")
+
+# This is the table
+let result = newDict()
+let id = 0
+# Lets loop over every device
+for device, deviceSessions in bgpNeighbors{
+    # And each session on the devices
+    for ip, sessionData in deviceSessions{
+        let data = merge(sessionData)
+        # Add one to the ID
+        let id = id + 1
+        result[id] = newDict()
+        # This is where we add the various columns
+        result[id]["0. Device"] = device
+        result[id]["1. Status"] = data["bgpState"]["Name"]
+        result[id]["2. Peering Address"] = data["bgpPeerLocalAddr"]
+        result[id]["3. Neighbor Address"] = data["key"]
+        result[id]["4. Neighbor AS"] = data["bgpPeerAs"]["value"]
+        if dictHasKey(bgpPeerInfoStatusEntry, device) && dictHasKey(bgpPeerStatisticsEntry, device) {
+            let test = merge(bgpPeerInfoStatusEntry[device][ip])
+            for kafi, kval in test["bgpPeerAfiSafiActive"]{
+                if kval == true {
+                    afis[device] = bgpPeerAfiSafiActive[kafi]
+                    afi_name[device] = bgpPeerAfiSafiActiveName[kafi]
+                }
+            }
+            let bgpPeerAfiSafiStats = merge(bgpPeerStatisticsEntry[device])
+            if dictHasKey(afis, device){
+                result[id]["6. PfxRcd"] = bgpPeerAfiSafiStats[ip]["bgpPeerAfiSafiStats"][afis[device]]["prefixIn"]
+                result[id]["7. PfxAcc"] = bgpPeerAfiSafiStats[ip]["bgpPeerAfiSafiStats"][afis[device]]["prefixAcceptedIn"]
+            }
+            result[id]["8. Up/Down"] = str(duration(1000000000*round(num(now() - time(data["bgpPeerIntoOrOutOfEstablishedTime"]*1000000000))/1000000000)))
+        }
+    }
+}
+result
+
+
+IBGP Summary +
+
+

BGP Sessions Flaps

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+
+if str(_POD_NAME) == "" {
+    let data =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`[4h]
+} else {
+    let data =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`[4h] | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+
+let result = newDict()
+for sn, deviceValues in data{
+    let count = 0
+    for ip, tseries in deviceValues{
+        for timestamp, values in tseries {
+            if dictHasKey(values, "bgpState"){
+                if values["bgpState"]["Name"] == "Established"{
+                    let count = count +1
+                }
+            }
+        }
+    }
+    result[sn] = newDict() | setFields("Flaps", count-1)
+}
+result
+
+
+
+
+

BGP Historical state tracker

+
# BGP Session historical state tracker
+let data = `analytics:/Devices/<_bgpDevice>/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`[4h]
+let res = newDict()
+for ip, tseries in data {
+    for timestamp, values in tseries {
+        # only show selected neighbors or all if none selected
+        if length(_NeighborIP) == 0 || dictHasKey(_NeighborIP, ip){
+
+            if !dictHasKey(res, str(timestamp)) {
+                res[str(timestamp)] = newDict() | setFields(ip, dictHasKey(values, "bgpState") ? values["bgpState"]["Name"] : 0)
+            } else {
+                res[str(timestamp)][ip]  = dictHasKey(values, "bgpState") ? values["bgpState"]["Name"] : 0
+            }
+        }
+    }
+}
+res
+
+
+BGP Flaps and Historical state tracker +
+
+

BGP Syslog Messages

+
let data = `<_bgpDevice>:/Logs/var/log/messages`[4h] | field("line") | where(reMatch(_value, "BGP"))
+let logs = newDict()
+for timest, logentry in data {
+    logs[str(timest)] = newDict()
+    logs[str(timest)]["Log"] = logentry
+}
+logs
+
+
+BGP Syslog Messages +

Download the Dashboard JSON here

+
+
+
+

Capacity Planning Routing and Switching

+
+

High Density Leaf Switches Numbers (7050X3) Tagged with HDL Label

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/HDL/elements`)
+
+if str(_POD_NAME) == "" {
+    let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(length(_value) == 0 ? 0 : _value)
+    let arpCount = `*:/Smash/arp/status/_counts`  | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+} else {
+    let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(length(_value) == 0 ? 0 : _value)
+    let arpCount = `*:/Smash/arp/status/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+
+let smash = macStatusCount | map(merge(_value) | fields("smashFdbStatus"))
+let nbvlan = macConfigCount | map(merge(_value) | fields("vlanConfig"))
+let nbVRF = vrfCount | map(length(merge(_value)))
+let nbARP = arpCount | map(merge(_value))
+
+for deviceKey, deviceValue in nbVRF {
+    let nbr = newDict()
+    nbr["VRF number"]=deviceValue
+    nbVRF[deviceKey]= nbr
+}
+
+for devicekey, devicevalue in smash {
+    smash[devicekey]["VLAN count"]=nbvlan[devicekey]["vlanConfig"]
+    smash[devicekey]["VRF count"]=nbVRF[devicekey]["VRF number"]
+    smash[devicekey]["ARP count"]=nbARP[devicekey]["arpEntry"]
+    smash[devicekey]["ND count"]=nbARP[devicekey]["neighborEntry"]
+}
+
+smash | map(_value | renameFields("smashFdbStatus","MAC count"))
+
+
+
+
+

Low Density Leaf Switches Numbers (7020R) Tagged with the LDL Label

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/LDL/elements`)
+
+if str(_POD_NAME) == "" {
+    let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(length(_value) == 0 ? 0 : _value)
+    let arpCount = `*:/Smash/arp/status/_counts`  | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+} else {
+    let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(length(_value) == 0 ? 0 : _value)
+    let arpCount = `*:/Smash/arp/status/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+
+let smash = macStatusCount | map(merge(_value) | fields("smashFdbStatus"))
+let nbvlan = macConfigCount | map(merge(_value) | fields("vlanConfig"))
+let nbVRF = vrfCount | map(length(merge(_value)))
+let nbARP = arpCount | map(merge(_value))
+
+for deviceKey, deviceValue in nbVRF {
+    let nbr = newDict()
+    nbr["VRF number"] = deviceValue
+    nbVRF[deviceKey] = nbr
+}
+
+for devicekey, devicevalue in smash {
+    smash[devicekey]["VLAN count"] = nbvlan[devicekey]["vlanConfig"]
+    smash[devicekey]["VRF count"] = dictHasKey(nbVRF, devicekey)? nbVRF[devicekey]["VRF number"] : 0
+    smash[devicekey]["ARP count"] = nbARP[devicekey]["arpEntry"]
+    smash[devicekey]["ND count"] = nbARP[devicekey]["neighborEntry"]
+}
+
+smash | map(_value | renameFields("smashFdbStatus","MAC count"))
+
+
+
+
+

EVPN Gateways Numbers (7280R2)

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/EGW/elements`)
+
+if str(_POD_NAME) == "" {
+    let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(length(_value) == 0 ? 0 : _value)
+    let arpCount = `*:/Smash/arp/status/_counts`  | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+} else {
+    let macConfigCount =`*:/Sysdb/bridging/config/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let macStatusCount =`*:/Smash/bridging/status/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    let vrfCount =`*:/Sysdb/routing/vrf/config/vrfConfig` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(length(_value) == 0 ? 0 : _value)
+    let arpCount = `*:/Smash/arp/status/_counts` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+
+let smash = macStatusCount | map(merge(_value) | fields("smashFdbStatus"))
+let nbvlan = macConfigCount | map(merge(_value) | fields("vlanConfig"))
+let nbVRF = vrfCount | map(length(merge(_value)))
+let nbARP = arpCount | map(merge(_value))
+
+for deviceKey, deviceValue in nbVRF {
+    let nbr = newDict()
+    nbr["VRF number"]=deviceValue
+    nbVRF[deviceKey]= nbr
+}
+
+for devicekey, devicevalue in smash {
+    smash[devicekey]["VLAN count"]=nbvlan[devicekey]["vlanConfig"]
+    smash[devicekey]["VRF count"]=nbVRF[devicekey]["VRF number"]
+    smash[devicekey]["ARP count"]=nbARP[devicekey]["arpEntry"]
+    smash[devicekey]["ND count"]=nbARP[devicekey]["neighborEntry"]
+}
+
+smash | map(_value | renameFields("smashFdbStatus","MAC count"))
+
+
+

Download the Dashboard JSON here

+
+
+
+

IGMP Snooping Table

+
+

Note

+

IGMP Snooping states are not streamed by default and the “/Sysdb/bridging/igmpsnooping” needs to be added to the TerminAttr include list using the tastreaming.TerminattrStreaming service API. +Examples can be found on Examples

+
+
let igmpSnooping = `<_device>:/Sysdb/bridging/igmpsnooping/forwarding/status/vlanStatus/*/ethGroup/*/intf`
+let filteredIgmpSnooping = igmpSnooping | recmap(2, merge(_value))
+let result = newDict()
+for vlanKey, macAddrIntf in filteredIgmpSnooping {
+    for macAddr, intfState in macAddrIntf {
+        # Create a list of interfaces a vlan is mapped to
+        let interfaceList = ""
+        if length(intfState) > 1{
+            for interface, intfBool in intfState {
+                let interfaceList = interfaceList + str(interface) + ", "
+            }
+        } else {
+            # if there is only 1 interface no need to add a comma
+            let interfaceList = str(dictKeys(intfState)[0])
+        }
+        let vlan = reFindCaptures(str(vlanKey), "{\"value\":(\d+)}")[0][1]
+        result[macAddr] = newDict() | setFields("VLAN", vlan, "Members", interfaceList)
+    }
+}
+result
+
+
+IGMP Snooping Table +

Download the Dashboard JSON here

+
+
+

Number of MAC addresses across all devices

+
let data = `*:/Smash/bridging/status/_counts`
+sum(data | map(merge(_value)) | where(dictHasKey(_value, "smashFdbStatus")) | map(_value["smashFdbStatus"]))
+
+
+Number of MAC addresses across all devices +

Download the Dashboard JSON here

+
+
+

Number of MACs per device per interface

+
let data = merge(`<_device>:/Smash/bridging/status/smashFdbStatus`)
+let numberMAC = newDict()
+
+for deviceKey, deviceValue in data {
+   if dictHasKey(numberMAC, data[deviceKey]["intf"]) {
+       numberMAC[data[deviceKey]["intf"]]["MACs"] = numberMAC[data[deviceKey]["intf"]]["MACs"] + 1
+   } else {
+       numberMAC[data[deviceKey]["intf"]] = newDict()
+       numberMAC[data[deviceKey]["intf"]]["MACs"] = 1
+   }
+}
+
+numberMAC
+
+
+Number of MACs per device per interface +

Download the Dashboard JSON here

+
+
+

List of Configured VLANs per VRFs

+
# Get the VRF configuration for all routed interfaces for all devices
+let data=`*:/Sysdb/l3/intf/config/intfConfig/*`
+
+# Build a new dictionary and select only the SVIs that are configured in a specific VRF
+let res = newDict()
+let id = 0
+for deviceKey, deviceValue in data {
+    for interfaceKey, interfaceValue in deviceValue {
+        if strContains(interfaceKey, "Vlan"){
+            let data1 = merge(interfaceValue)
+            if data1["vrf"]["value"] == _VRF {
+                let id = id + 1
+                res[id] = newDict()
+                res[id]["Device"] = deviceKey
+                res[id]["Interfaces"] = interfaceKey
+                res[id]["VRFs"] = _VRF
+            }
+        }
+    }
+}
+res
+
+
+
# Get the VRF configuration for all routed interfaces for all devices
+let data=`*:/Sysdb/l3/intf/config/intfConfig/*`
+
+# Build a new dictionary and select only the SVIs and the VRFs they are configured in
+let res = newDict()
+let id = 0
+for deviceKey, deviceValue in data {
+    for interfaceKey, interfaceValue in deviceValue {
+        if strContains(interfaceKey, "Vlan"){
+            let id = id + 1
+            res[id] = newDict()
+            res[id]["Device"] = deviceKey
+            res[id]["Interfaces"] = interfaceKey
+            res[id]["VRFs"] = merge(interfaceValue)["vrf"]["value"]
+        }
+    }
+}
+res
+
+
+List of Configured VLANs per VRF +

Download the Dashboard JSON here

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/layer2_and_layer3/mac_entries.html b/examples/layer2_and_layer3/mac_entries.html new file mode 100644 index 0000000..63159d9 --- /dev/null +++ b/examples/layer2_and_layer3/mac_entries.html @@ -0,0 +1,117 @@ + + + + + + + Number of MAC addresses across all devices — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Number of MAC addresses across all devices

+
let data = `*:/Smash/bridging/status/_counts`
+sum(data | map(merge(_value)) | where(dictHasKey(_value, "smashFdbStatus")) | map(_value["smashFdbStatus"]))
+
+
+Number of MAC addresses across all devices +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/layer2_and_layer3/macs_per_device.html b/examples/layer2_and_layer3/macs_per_device.html new file mode 100644 index 0000000..fb79716 --- /dev/null +++ b/examples/layer2_and_layer3/macs_per_device.html @@ -0,0 +1,128 @@ + + + + + + + Number of MACs per device per interface — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Number of MACs per device per interface

+
let data = merge(`<_device>:/Smash/bridging/status/smashFdbStatus`)
+let numberMAC = newDict()
+
+for deviceKey, deviceValue in data {
+   if dictHasKey(numberMAC, data[deviceKey]["intf"]) {
+       numberMAC[data[deviceKey]["intf"]]["MACs"] = numberMAC[data[deviceKey]["intf"]]["MACs"] + 1
+   } else {
+       numberMAC[data[deviceKey]["intf"]] = newDict()
+       numberMAC[data[deviceKey]["intf"]]["MACs"] = 1
+   }
+}
+
+numberMAC
+
+
+Number of MACs per device per interface +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/layer2_and_layer3/vrfs_in_vlans.html b/examples/layer2_and_layer3/vrfs_in_vlans.html new file mode 100644 index 0000000..7e9457e --- /dev/null +++ b/examples/layer2_and_layer3/vrfs_in_vlans.html @@ -0,0 +1,156 @@ + + + + + + + List of Configured VLANs per VRFs — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

List of Configured VLANs per VRFs

+
# Get the VRF configuration for all routed interfaces for all devices
+let data=`*:/Sysdb/l3/intf/config/intfConfig/*`
+
+# Build a new dictionary and select only the SVIs that are configured in a specific VRF
+let res = newDict()
+let id = 0
+for deviceKey, deviceValue in data {
+    for interfaceKey, interfaceValue in deviceValue {
+        if strContains(interfaceKey, "Vlan"){
+            let data1 = merge(interfaceValue)
+            if data1["vrf"]["value"] == _VRF {
+                let id = id + 1
+                res[id] = newDict()
+                res[id]["Device"] = deviceKey
+                res[id]["Interfaces"] = interfaceKey
+                res[id]["VRFs"] = _VRF
+            }
+        }
+    }
+}
+res
+
+
+
# Get the VRF configuration for all routed interfaces for all devices
+let data=`*:/Sysdb/l3/intf/config/intfConfig/*`
+
+# Build a new dictionary and select only the SVIs and the VRFs they are configured in
+let res = newDict()
+let id = 0
+for deviceKey, deviceValue in data {
+    for interfaceKey, interfaceValue in deviceValue {
+        if strContains(interfaceKey, "Vlan"){
+            let id = id + 1
+            res[id] = newDict()
+            res[id]["Device"] = deviceKey
+            res[id]["Interfaces"] = interfaceKey
+            res[id]["VRFs"] = merge(interfaceValue)["vrf"]["value"]
+        }
+    }
+}
+res
+
+
+List of Configured VLANs per VRF +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/layer2_and_layer3/webinar03_bgp.html b/examples/layer2_and_layer3/webinar03_bgp.html new file mode 100644 index 0000000..07f47ee --- /dev/null +++ b/examples/layer2_and_layer3/webinar03_bgp.html @@ -0,0 +1,230 @@ + + + + + + + TAC Webinar03 2023 - BGP States — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

TAC Webinar03 2023 - BGP States

+
+

BGP Summary

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let bgpPeerInfoStatusEntry = `*:/Sysdb/cell/1/routing/bgp/export/vrfBgpPeerInfoStatusEntryTable/default/bgpPeerInfoStatusEntry/*`
+if str(_POD_NAME) == "" {
+    let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`
+} else {
+    let bgpNeighbors =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+let bgpPeerStatisticsEntry = `*:/Smash/routing/bgp/bgpPeerInfoStatus/default/bgpPeerStatisticsEntry`
+let afis = newDict()
+let afi_name = newDict()
+let bgpPeerAfiSafiActive = newDict() | setFields("l2vpnEvpn", 3, "ipv4Unicast", 1)
+let bgpPeerAfiSafiActiveName = newDict() | setFields("l2vpnEvpn","L2VPN EVPN", "ipv4Unicast", "IPv4 Unicast")
+
+# This is the table
+let result = newDict()
+let id = 0
+# Lets loop over every device
+for device, deviceSessions in bgpNeighbors{
+    # And each session on the devices
+    for ip, sessionData in deviceSessions{
+        let data = merge(sessionData)
+        # Add one to the ID
+        let id = id + 1
+        result[id] = newDict()
+        # This is where we add the various columns
+        result[id]["0. Device"] = device
+        result[id]["1. Status"] = data["bgpState"]["Name"]
+        result[id]["2. Peering Address"] = data["bgpPeerLocalAddr"]
+        result[id]["3. Neighbor Address"] = data["key"]
+        result[id]["4. Neighbor AS"] = data["bgpPeerAs"]["value"]
+        if dictHasKey(bgpPeerInfoStatusEntry, device) && dictHasKey(bgpPeerStatisticsEntry, device) {
+            let test = merge(bgpPeerInfoStatusEntry[device][ip])
+            for kafi, kval in test["bgpPeerAfiSafiActive"]{
+                if kval == true {
+                    afis[device] = bgpPeerAfiSafiActive[kafi]
+                    afi_name[device] = bgpPeerAfiSafiActiveName[kafi]
+                }
+            }
+            let bgpPeerAfiSafiStats = merge(bgpPeerStatisticsEntry[device])
+            if dictHasKey(afis, device){
+                result[id]["6. PfxRcd"] = bgpPeerAfiSafiStats[ip]["bgpPeerAfiSafiStats"][afis[device]]["prefixIn"]
+                result[id]["7. PfxAcc"] = bgpPeerAfiSafiStats[ip]["bgpPeerAfiSafiStats"][afis[device]]["prefixAcceptedIn"]
+            }
+            result[id]["8. Up/Down"] = str(duration(1000000000*round(num(now() - time(data["bgpPeerIntoOrOutOfEstablishedTime"]*1000000000))/1000000000)))
+        }
+    }
+}
+result
+
+
+IBGP Summary +
+
+

BGP Sessions Flaps

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+
+if str(_POD_NAME) == "" {
+    let data =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`[4h]
+} else {
+    let data =`analytics:/Devices/*/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`[4h] | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+}
+
+let result = newDict()
+for sn, deviceValues in data{
+    let count = 0
+    for ip, tseries in deviceValues{
+        for timestamp, values in tseries {
+            if dictHasKey(values, "bgpState"){
+                if values["bgpState"]["Name"] == "Established"{
+                    let count = count +1
+                }
+            }
+        }
+    }
+    result[sn] = newDict() | setFields("Flaps", count-1)
+}
+result
+
+
+
+
+

BGP Historical state tracker

+
# BGP Session historical state tracker
+let data = `analytics:/Devices/<_bgpDevice>/versioned-data/routing/bgp/status/vrf/default/bgpPeerInfoStatusEntry/*`[4h]
+let res = newDict()
+for ip, tseries in data {
+    for timestamp, values in tseries {
+        # only show selected neighbors or all if none selected
+        if length(_NeighborIP) == 0 || dictHasKey(_NeighborIP, ip){
+
+            if !dictHasKey(res, str(timestamp)) {
+                res[str(timestamp)] = newDict() | setFields(ip, dictHasKey(values, "bgpState") ? values["bgpState"]["Name"] : 0)
+            } else {
+                res[str(timestamp)][ip]  = dictHasKey(values, "bgpState") ? values["bgpState"]["Name"] : 0
+            }
+        }
+    }
+}
+res
+
+
+BGP Flaps and Historical state tracker +
+
+

BGP Syslog Messages

+
let data = `<_bgpDevice>:/Logs/var/log/messages`[4h] | field("line") | where(reMatch(_value, "BGP"))
+let logs = newDict()
+for timest, logentry in data {
+    logs[str(timest)] = newDict()
+    logs[str(timest)]["Log"] = logentry
+}
+logs
+
+
+BGP Syslog Messages +

Download the Dashboard JSON here

+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/system_health/abootfn44.html b/examples/system_health/abootfn44.html new file mode 100644 index 0000000..d140829 --- /dev/null +++ b/examples/system_health/abootfn44.html @@ -0,0 +1,169 @@ + + + + + + + Listing of devices affected by FN44 — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Listing of devices affected by FN44

+
let fixed = `*:/Sysdb/hardware/entmib/fixedSystem`
+let chassis = `*:/Sysdb/hardware/entmib/chassis/cardSlot/*/card`
+let result = newDict()
+
+for deviceKey, deviceValue in fixed{
+    let fx_temp = merge(deviceValue)
+    if dictHasKey(fx_temp , "firmwareRev") && fx_temp["firmwareRev"] != ""{
+        result[deviceKey] = newDict() | setFields("Aboot version", fx_temp["firmwareRev"])
+    }
+
+}
+for deviceKey, deviceValue in chassis {
+    let cx_tmp = chassis[deviceKey] | map(merge(_value))
+    for card in cx_tmp{
+        if dictHasKey(card, "firmwareRev") && card["firmwareRev"] != "" {
+            result[deviceKey] = newDict() | setFields("Aboot version", card["firmwareRev"])
+        }
+    }
+
+}
+
+let re = "Aboot-norcal\d+-(\d+)\.(\d+)\.(\d+).*"
+
+for deviceKey, deviceValue in result{
+    let aboot =  reFindCaptures(deviceValue["Aboot version"], re)
+    if length(aboot) > 0 {
+
+        if num(aboot[0][1]) == 4 {
+            if num(aboot[0][2]) == 0 && num(aboot[0][3]) < 7  {
+                result[deviceKey]["affected"] = "🔥 True"
+            }
+            if num(aboot[0][2]) == 1 && num(aboot[0][3]) < 1 {
+                result[deviceKey]["affected"] = "🔥 True"
+            }
+        }
+        if num(aboot[0][1]) == 6 {
+            if num(aboot[0][2]) == 0 && num(aboot[0][3]) < 9  {
+                result[deviceKey]["affected"] = "🔥 True"
+            }
+            if num(aboot[0][2]) == 1 && num(aboot[0][3]) < 7 {
+                result[deviceKey]["affected"] = "🔥 True"
+            } else {
+                result[deviceKey]["affected"] = "✅ False"
+            }
+        } else {
+            result[deviceKey]["affected"] = "✅ False"
+        }
+
+    } else {
+        result[deviceKey]["affected"] = "✅ False"
+    }
+
+}
+result
+
+
+Listing of devices affected by FN44 +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/system_health/eol_planning.html b/examples/system_health/eol_planning.html new file mode 100644 index 0000000..b1f24a4 --- /dev/null +++ b/examples/system_health/eol_planning.html @@ -0,0 +1,171 @@ + + + + + + + EoL Planning — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

EoL Planning

+
let inventory = `analytics:/DatasetInfo/Devices`
+let hardware = merge(`analytics:/lifecycles/hardware`)
+let softwareLife = merge(`analytics:/lifecycles/software`)
+let software = merge(`analytics:/lifecycles/devices/software`)
+let skus = merge(`analytics:/BugAlerts/skus`)
+
+let filteredSkus = newDict()
+for key, val in skus {
+    if  strContains(key, "DCS-") {
+        filteredSkus[key] = newDict()
+        let relNum = ""
+        if str(val["releaseDeprecated"]) != "[]" {
+            let deprecatedReleaseNum = strSplit(str(val["releaseDeprecated"]),",")[0]
+            let deprecatedReleaseNum = strReplace(deprecatedReleaseNum, "[","")
+            let deprecatedReleaseNum = strReplace(deprecatedReleaseNum, "]","")
+            let deprecatedReleaseNum = strReplace(deprecatedReleaseNum, "\"","")
+            let deprecatedReleaseNum = strSplit(deprecatedReleaseNum,".")
+            let relNum = deprecatedReleaseNum[0]+"."+str(num(deprecatedReleaseNum[1])-1)
+        }
+        filteredSkus[key]["releaseDeprecated"] = relNum
+    }
+}
+let inv = newDict()
+for deviceUpdate, device in inventory {
+    for deviceSN, deviceData in device {
+        inv[deviceSN] = newDict()
+        inv[deviceSN]["Hostname"] = deviceData["hostname"]
+        inv[deviceSN]["ModelName"] = deviceData["modelName"]
+        inv[deviceSN]["Version"] = deviceData["eosVersion"]
+        inv[deviceSN]["TerminAttr"] = deviceData["terminAttrVersion"]
+        for hw, hwEol in hardware {
+            if  deviceData["modelName"] == hw {
+                inv[deviceSN]["Hardware EndOfSale"] = hwEol["endOfSale"]
+                inv[deviceSN]["Hardware EndOfTACSupport"] = hwEol["endOfTACSupport"]
+                inv[deviceSN]["Hardware EndOfRMARequests"] = hwEol["endOfHardwareRMARequests"]
+                inv[deviceSN]["Hardware EndOfLife"] = hwEol["endOfLife"]
+            }
+        }
+        for switch, switchEol in software {
+            if  switch == deviceSN {
+                inv[deviceSN]["Current Software EndOfLife"] = switchEol["endOfSupport"]
+            }
+        }
+        for skuKey, skuVal in filteredSkus {
+            if  strContains(skuKey, deviceData["modelName"]) {
+                inv[deviceSN]["Last Supported Software Train"] = skuVal["releaseDeprecated"]
+            }
+        }
+        for sw, swEol in softwareLife {
+            if  dictHasKey(inv[deviceSN],"Last Supported Software Train") && sw == inv[deviceSN]["Last Supported Software Train"] {
+                inv[deviceSN]["Last Supported Software Train EndOfLife"] = swEol["endOfSupport"]
+            }
+        }
+    }
+}
+inv
+
+
+EoL Planning +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/system_health/fn72.html b/examples/system_health/fn72.html new file mode 100644 index 0000000..00606c4 --- /dev/null +++ b/examples/system_health/fn72.html @@ -0,0 +1,164 @@ + + + + + + + Listing of devices affected by FN72 — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Listing of devices affected by FN72

+
let inventory = merge(`analytics:/DatasetInfo/Devices`)
+let affected = newDict()
+let id = 1
+for dkey, dval in inventory{
+
+    let serial_slice = strCut(dkey,3,7)
+    let model = dval["modelName"]
+    let hostname = dval["hostname"]
+    if strContains(model, "7280SR3-48YC8"){
+        if strHasPrefix(dkey, "JPE"){
+            if num(serial_slice) < 2131{
+                affected[id] = newDict()
+                affected[id]["Hostname"] = hostname
+                affected[id]["Serial Number"] = dkey
+                affected[id]["Model"] = model
+            }
+        }
+        if strHasPrefix(dkey,"JAS"){
+            if num(serial_slice) < 2041{
+                affected[id] = newDict()
+                affected[id]["Hostname"] = hostname
+                affected[id]["Serial Number"] = dkey
+                affected[id]["Model"] = model
+            }
+        }
+        let id = id + 1
+    }
+    if strContains(model, "7280SR3K-48YC8"){
+        if strHasPrefix(dkey, "JPE"){
+            if num(serial_slice) < 2134{
+                affected[id] = newDict()
+                affected[id]["Hostname"] = hostname
+                affected[id]["Serial Number"] = dkey
+                affected[id]["Model"] = model
+            }
+        }
+        if strHasPrefix(dkey, "JAS"){
+            if num(serial_slice) < 2041{
+                affected[id] = newDict()
+                affected[id]["Hostname"] = hostname
+                affected[id]["Serial Number"] = dkey
+                affected[id]["Model"] = model
+            }
+        }
+        let id = id + 1
+    }
+
+}
+affected
+
+
+Listing of devices affected by FN72 +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/system_health/hardware_health_check.html b/examples/system_health/hardware_health_check.html new file mode 100644 index 0000000..83aa406 --- /dev/null +++ b/examples/system_health/hardware_health_check.html @@ -0,0 +1,219 @@ + + + + + + + Hardware Health Check — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Hardware Health Check

+
+

Power Supply Status per Device

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/<_SWITCH_ROLE>/elements`)
+
+
+let powerSupply = `*:/Sysdb/environment/archer/power/status/powerSupply/*`
+let filteredPsuStats = powerSupply | where(dictHasKey(powerSupply, _key) == true) | recmap(2, (merge(_value)["state"]["Name"]))
+
+if str(_POD_NAME) == "" && str(_SWITCH_ROLE) == "" {
+    let result = filteredPsuStats
+} else {
+    if str(_SWITCH_ROLE) == "" {
+        let result = filteredPsuStats | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    } else {
+        if str(_POD_NAME) == "" {
+            let result = filteredPsuStats | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        } else {
+            let result = filteredPsuStats | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        }
+    }
+}
+
+for deviceKey, deviceValue  in result{
+    for powerSupplyKey, powerSupplyValue in deviceValue{
+        if powerSupplyValue=="ok"{
+            result[deviceKey][powerSupplyKey]="✔️"
+        } else{
+            result[deviceKey][powerSupplyKey]="❌"
+        }
+    }
+}
+result
+
+
+
+
+

Fan Status per Device

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/<_SWITCH_ROLE>/elements`)
+
+let fans = `*:/Sysdb/environment/archer/cooling/status/*`
+let fansHwStatus = fans | where(dictHasKey(fans, _key) == true) | recmap(2, (merge(_value)["hwStatus"]["Name"]))
+
+if str(_POD_NAME) == "" && str(_SWITCH_ROLE) == "" {
+    let result = fansHwStatus
+} else {
+    if str(_SWITCH_ROLE) == "" {
+        let result = fansHwStatus | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    } else {
+        if str(_POD_NAME) == "" {
+            let result = fansHwStatus | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        } else {
+            let result = fansHwStatus | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        }
+    }
+}
+
+for deviceKey, deviceValue  in result{
+    for fansKey, fansValue in deviceValue{
+        if fansValue=="ok"{
+            result[deviceKey][fansKey]="✔️"
+        } else{
+            result[deviceKey][fansKey]="❌"
+        }
+    }
+}
+result
+
+
+
+
+

Temperature Sensors per Device

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/<_SWITCH_ROLE>/elements`)
+
+let temperature = `*:/Sysdb/environment/archer/temperature/status/cell/1/*`
+let filteredTemperatureStats = temperature | where(dictHasKey(temperature, _key) == true) | recmap(2, (merge(_value)["alertRaised"]))
+
+if str(_POD_NAME) == "" && str(_SWITCH_ROLE) == "" {
+    let result = filteredTemperatureStats
+} else {
+    if str(_SWITCH_ROLE) == "" {
+        let result = filteredTemperatureStats | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    } else {
+        if str(_POD_NAME) == "" {
+            let result = filteredTemperatureStats | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        } else {
+            let result = filteredTemperatureStats | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        }
+    }
+}
+
+for deviceKey, deviceValue  in result{
+    for sensorKey, sensorValue in deviceValue{
+        if sensorValue==false{
+            result[deviceKey][sensorKey]="✔️"
+        } else{
+            result[deviceKey][sensorKey]="❌"
+        }
+    }
+}
+result
+
+
+Hardware Health Check +

Download the Dashboard JSON here

+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/system_health/important_pod_count.html b/examples/system_health/important_pod_count.html new file mode 100644 index 0000000..e7110df --- /dev/null +++ b/examples/system_health/important_pod_count.html @@ -0,0 +1,202 @@ + + + + + + + Important Pod Count — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Important Pod Count

+
+

Number of Leaf Switches

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesInCPOD1 = merge(`analytics:/tags/labels/devices/pod_name/value/CPOD1/elements`)
+
+if str(_POD_NAME) == "" {
+    let leafs = length(devicesInCPOD1) - 2
+} else {
+    let leafs = length(devices) - 2
+}
+
+leafs
+
+
+
+
+

Number of VTEPs

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesInCPOD1 = merge(`analytics:/tags/labels/devices/pod_name/value/CPOD1/elements`)
+
+if str(_POD_NAME) == "" {
+    let vteps = (length(devicesInCPOD1) - 2 ) / 2
+} else {
+    if strContains(str(_POD_NAME), "SPOD") {
+        let vteps = ((length(devices) - 3) / 2)+1
+    } else {
+    let vteps = (length(devices) - 2) / 2
+    }
+}
+
+vteps
+
+
+
+
+

Total VLAN Count

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesInCPOD1 = merge(`analytics:/tags/labels/devices/pod_name/value/CPOD1/elements`)
+
+if str(_POD_NAME) == "" {
+    let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devicesInCPOD1, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value))
+} else {
+    let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value))
+}
+
+let numberVlan = newDict()
+
+for deviceKey, deviceValue in vlanConfig {
+    for vlanKey, vlanValue in deviceValue{
+        numberVlan[vlanKey["value"]] = 1
+    }
+}
+
+length(numberVlan)
+
+
+
+
+

Max Size of the Floodlist

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesInCPOD1 = merge(`analytics:/tags/labels/devices/pod_name/value/CPOD1/elements`)
+
+if str(_POD_NAME) == "" {
+    let vteps = (length(devicesCPOD1) -2) / 2
+    let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devicesInCPOD1, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value))
+} else {
+    if strContains(str(_POD_NAME), "SPOD") {
+        let vteps = ((length(devices) - 3) / 2)+1
+        let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value))
+    } else {
+    let vteps = (length(devices) -2) / 2
+    let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value))
+    }
+}
+
+let numberVlan = newDict()
+
+for deviceKey, deviceValue in vlanConfig {
+    for vlanKey, vlanValue in deviceValue{
+        numberVlan[vlanKey["value"]] = 1
+    }
+}
+
+let vlans = length(numberVlan)
+
+vteps*vlans
+
+
+ImportantPodCount +

Download the Dashboard JSON here

+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/system_health/index.html b/examples/system_health/index.html new file mode 100644 index 0000000..65fce1c --- /dev/null +++ b/examples/system_health/index.html @@ -0,0 +1,810 @@ + + + + + + + System Health Examples — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

System Health Examples

+
+

Listing of devices affected by FN44

+
let fixed = `*:/Sysdb/hardware/entmib/fixedSystem`
+let chassis = `*:/Sysdb/hardware/entmib/chassis/cardSlot/*/card`
+let result = newDict()
+
+for deviceKey, deviceValue in fixed{
+    let fx_temp = merge(deviceValue)
+    if dictHasKey(fx_temp , "firmwareRev") && fx_temp["firmwareRev"] != ""{
+        result[deviceKey] = newDict() | setFields("Aboot version", fx_temp["firmwareRev"])
+    }
+
+}
+for deviceKey, deviceValue in chassis {
+    let cx_tmp = chassis[deviceKey] | map(merge(_value))
+    for card in cx_tmp{
+        if dictHasKey(card, "firmwareRev") && card["firmwareRev"] != "" {
+            result[deviceKey] = newDict() | setFields("Aboot version", card["firmwareRev"])
+        }
+    }
+
+}
+
+let re = "Aboot-norcal\d+-(\d+)\.(\d+)\.(\d+).*"
+
+for deviceKey, deviceValue in result{
+    let aboot =  reFindCaptures(deviceValue["Aboot version"], re)
+    if length(aboot) > 0 {
+
+        if num(aboot[0][1]) == 4 {
+            if num(aboot[0][2]) == 0 && num(aboot[0][3]) < 7  {
+                result[deviceKey]["affected"] = "🔥 True"
+            }
+            if num(aboot[0][2]) == 1 && num(aboot[0][3]) < 1 {
+                result[deviceKey]["affected"] = "🔥 True"
+            }
+        }
+        if num(aboot[0][1]) == 6 {
+            if num(aboot[0][2]) == 0 && num(aboot[0][3]) < 9  {
+                result[deviceKey]["affected"] = "🔥 True"
+            }
+            if num(aboot[0][2]) == 1 && num(aboot[0][3]) < 7 {
+                result[deviceKey]["affected"] = "🔥 True"
+            } else {
+                result[deviceKey]["affected"] = "✅ False"
+            }
+        } else {
+            result[deviceKey]["affected"] = "✅ False"
+        }
+
+    } else {
+        result[deviceKey]["affected"] = "✅ False"
+    }
+
+}
+result
+
+
+Listing of devices affected by FN44 +

Download the Dashboard JSON here

+
+
+

EoL Planning

+
let inventory = `analytics:/DatasetInfo/Devices`
+let hardware = merge(`analytics:/lifecycles/hardware`)
+let softwareLife = merge(`analytics:/lifecycles/software`)
+let software = merge(`analytics:/lifecycles/devices/software`)
+let skus = merge(`analytics:/BugAlerts/skus`)
+
+let filteredSkus = newDict()
+for key, val in skus {
+    if  strContains(key, "DCS-") {
+        filteredSkus[key] = newDict()
+        let relNum = ""
+        if str(val["releaseDeprecated"]) != "[]" {
+            let deprecatedReleaseNum = strSplit(str(val["releaseDeprecated"]),",")[0]
+            let deprecatedReleaseNum = strReplace(deprecatedReleaseNum, "[","")
+            let deprecatedReleaseNum = strReplace(deprecatedReleaseNum, "]","")
+            let deprecatedReleaseNum = strReplace(deprecatedReleaseNum, "\"","")
+            let deprecatedReleaseNum = strSplit(deprecatedReleaseNum,".")
+            let relNum = deprecatedReleaseNum[0]+"."+str(num(deprecatedReleaseNum[1])-1)
+        }
+        filteredSkus[key]["releaseDeprecated"] = relNum
+    }
+}
+let inv = newDict()
+for deviceUpdate, device in inventory {
+    for deviceSN, deviceData in device {
+        inv[deviceSN] = newDict()
+        inv[deviceSN]["Hostname"] = deviceData["hostname"]
+        inv[deviceSN]["ModelName"] = deviceData["modelName"]
+        inv[deviceSN]["Version"] = deviceData["eosVersion"]
+        inv[deviceSN]["TerminAttr"] = deviceData["terminAttrVersion"]
+        for hw, hwEol in hardware {
+            if  deviceData["modelName"] == hw {
+                inv[deviceSN]["Hardware EndOfSale"] = hwEol["endOfSale"]
+                inv[deviceSN]["Hardware EndOfTACSupport"] = hwEol["endOfTACSupport"]
+                inv[deviceSN]["Hardware EndOfRMARequests"] = hwEol["endOfHardwareRMARequests"]
+                inv[deviceSN]["Hardware EndOfLife"] = hwEol["endOfLife"]
+            }
+        }
+        for switch, switchEol in software {
+            if  switch == deviceSN {
+                inv[deviceSN]["Current Software EndOfLife"] = switchEol["endOfSupport"]
+            }
+        }
+        for skuKey, skuVal in filteredSkus {
+            if  strContains(skuKey, deviceData["modelName"]) {
+                inv[deviceSN]["Last Supported Software Train"] = skuVal["releaseDeprecated"]
+            }
+        }
+        for sw, swEol in softwareLife {
+            if  dictHasKey(inv[deviceSN],"Last Supported Software Train") && sw == inv[deviceSN]["Last Supported Software Train"] {
+                inv[deviceSN]["Last Supported Software Train EndOfLife"] = swEol["endOfSupport"]
+            }
+        }
+    }
+}
+inv
+
+
+EoL Planning +

Download the Dashboard JSON here

+
+
+

Listing of devices affected by FN72

+
let inventory = merge(`analytics:/DatasetInfo/Devices`)
+let affected = newDict()
+let id = 1
+for dkey, dval in inventory{
+
+    let serial_slice = strCut(dkey,3,7)
+    let model = dval["modelName"]
+    let hostname = dval["hostname"]
+    if strContains(model, "7280SR3-48YC8"){
+        if strHasPrefix(dkey, "JPE"){
+            if num(serial_slice) < 2131{
+                affected[id] = newDict()
+                affected[id]["Hostname"] = hostname
+                affected[id]["Serial Number"] = dkey
+                affected[id]["Model"] = model
+            }
+        }
+        if strHasPrefix(dkey,"JAS"){
+            if num(serial_slice) < 2041{
+                affected[id] = newDict()
+                affected[id]["Hostname"] = hostname
+                affected[id]["Serial Number"] = dkey
+                affected[id]["Model"] = model
+            }
+        }
+        let id = id + 1
+    }
+    if strContains(model, "7280SR3K-48YC8"){
+        if strHasPrefix(dkey, "JPE"){
+            if num(serial_slice) < 2134{
+                affected[id] = newDict()
+                affected[id]["Hostname"] = hostname
+                affected[id]["Serial Number"] = dkey
+                affected[id]["Model"] = model
+            }
+        }
+        if strHasPrefix(dkey, "JAS"){
+            if num(serial_slice) < 2041{
+                affected[id] = newDict()
+                affected[id]["Hostname"] = hostname
+                affected[id]["Serial Number"] = dkey
+                affected[id]["Model"] = model
+            }
+        }
+        let id = id + 1
+    }
+
+}
+affected
+
+
+Listing of devices affected by FN72 +

Download the Dashboard JSON here

+
+
+

Hardware Health Check

+
+

Power Supply Status per Device

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/<_SWITCH_ROLE>/elements`)
+
+
+let powerSupply = `*:/Sysdb/environment/archer/power/status/powerSupply/*`
+let filteredPsuStats = powerSupply | where(dictHasKey(powerSupply, _key) == true) | recmap(2, (merge(_value)["state"]["Name"]))
+
+if str(_POD_NAME) == "" && str(_SWITCH_ROLE) == "" {
+    let result = filteredPsuStats
+} else {
+    if str(_SWITCH_ROLE) == "" {
+        let result = filteredPsuStats | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    } else {
+        if str(_POD_NAME) == "" {
+            let result = filteredPsuStats | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        } else {
+            let result = filteredPsuStats | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        }
+    }
+}
+
+for deviceKey, deviceValue  in result{
+    for powerSupplyKey, powerSupplyValue in deviceValue{
+        if powerSupplyValue=="ok"{
+            result[deviceKey][powerSupplyKey]="✔️"
+        } else{
+            result[deviceKey][powerSupplyKey]="❌"
+        }
+    }
+}
+result
+
+
+
+
+

Fan Status per Device

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/<_SWITCH_ROLE>/elements`)
+
+let fans = `*:/Sysdb/environment/archer/cooling/status/*`
+let fansHwStatus = fans | where(dictHasKey(fans, _key) == true) | recmap(2, (merge(_value)["hwStatus"]["Name"]))
+
+if str(_POD_NAME) == "" && str(_SWITCH_ROLE) == "" {
+    let result = fansHwStatus
+} else {
+    if str(_SWITCH_ROLE) == "" {
+        let result = fansHwStatus | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    } else {
+        if str(_POD_NAME) == "" {
+            let result = fansHwStatus | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        } else {
+            let result = fansHwStatus | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        }
+    }
+}
+
+for deviceKey, deviceValue  in result{
+    for fansKey, fansValue in deviceValue{
+        if fansValue=="ok"{
+            result[deviceKey][fansKey]="✔️"
+        } else{
+            result[deviceKey][fansKey]="❌"
+        }
+    }
+}
+result
+
+
+
+
+

Temperature Sensors per Device

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/<_SWITCH_ROLE>/elements`)
+
+let temperature = `*:/Sysdb/environment/archer/temperature/status/cell/1/*`
+let filteredTemperatureStats = temperature | where(dictHasKey(temperature, _key) == true) | recmap(2, (merge(_value)["alertRaised"]))
+
+if str(_POD_NAME) == "" && str(_SWITCH_ROLE) == "" {
+    let result = filteredTemperatureStats
+} else {
+    if str(_SWITCH_ROLE) == "" {
+        let result = filteredTemperatureStats | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")))
+    } else {
+        if str(_POD_NAME) == "" {
+            let result = filteredTemperatureStats | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        } else {
+            let result = filteredTemperatureStats | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        }
+    }
+}
+
+for deviceKey, deviceValue  in result{
+    for sensorKey, sensorValue in deviceValue{
+        if sensorValue==false{
+            result[deviceKey][sensorKey]="✔️"
+        } else{
+            result[deviceKey][sensorKey]="❌"
+        }
+    }
+}
+result
+
+
+Hardware Health Check +

Download the Dashboard JSON here

+
+
+
+

Important Pod Count

+
+

Number of Leaf Switches

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesInCPOD1 = merge(`analytics:/tags/labels/devices/pod_name/value/CPOD1/elements`)
+
+if str(_POD_NAME) == "" {
+    let leafs = length(devicesInCPOD1) - 2
+} else {
+    let leafs = length(devices) - 2
+}
+
+leafs
+
+
+
+
+

Number of VTEPs

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesInCPOD1 = merge(`analytics:/tags/labels/devices/pod_name/value/CPOD1/elements`)
+
+if str(_POD_NAME) == "" {
+    let vteps = (length(devicesInCPOD1) - 2 ) / 2
+} else {
+    if strContains(str(_POD_NAME), "SPOD") {
+        let vteps = ((length(devices) - 3) / 2)+1
+    } else {
+    let vteps = (length(devices) - 2) / 2
+    }
+}
+
+vteps
+
+
+
+
+

Total VLAN Count

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesInCPOD1 = merge(`analytics:/tags/labels/devices/pod_name/value/CPOD1/elements`)
+
+if str(_POD_NAME) == "" {
+    let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devicesInCPOD1, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value))
+} else {
+    let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value))
+}
+
+let numberVlan = newDict()
+
+for deviceKey, deviceValue in vlanConfig {
+    for vlanKey, vlanValue in deviceValue{
+        numberVlan[vlanKey["value"]] = 1
+    }
+}
+
+length(numberVlan)
+
+
+
+
+

Max Size of the Floodlist

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesInCPOD1 = merge(`analytics:/tags/labels/devices/pod_name/value/CPOD1/elements`)
+
+if str(_POD_NAME) == "" {
+    let vteps = (length(devicesCPOD1) -2) / 2
+    let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devicesInCPOD1, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value))
+} else {
+    if strContains(str(_POD_NAME), "SPOD") {
+        let vteps = ((length(devices) - 3) / 2)+1
+        let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value))
+    } else {
+    let vteps = (length(devices) -2) / 2
+    let vlanConfig = `*:/Sysdb/bridging/config/vlanConfig` | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value))
+    }
+}
+
+let numberVlan = newDict()
+
+for deviceKey, deviceValue in vlanConfig {
+    for vlanKey, vlanValue in deviceValue{
+        numberVlan[vlanKey["value"]] = 1
+    }
+}
+
+let vlans = length(numberVlan)
+
+vteps*vlans
+
+
+ImportantPodCount +

Download the Dashboard JSON here

+
+
+
+

NTP Stats

+
let ntpData = `*:/NTP/status/system/variables` | map(merge(_value))
+let output = newDict()
+for devID,devData in ntpData{
+    output[devID] = newDict()
+    output[devID]["peer"]= devData["refid"]
+    output[devID]["Stratum"]=devData["stratum"]
+    output[devID]["OffSet"]=devData["offset"]
+    }
+output
+
+
+NTP Stats +

Download the Dashboard JSON here

+
+
+

PowerSupply Output

+
let powerSupply = `analytics:/Devices/*/versioned-data/environment/power/aggregate/*/out/15m`[48h]
+let result = newDict()
+for key, value in _device{
+
+    if dictHasKey(powerSupply,key) {
+         for psk,psv in powerSupply[key]{
+             result[key+"-"+psk] = powerSupply[key][psk] | field("value") | field("avg")
+         }
+    }
+}
+result
+
+
+PowerSupply Output +

Download the Dashboard JSON here

+
+
+

System Health Check

+
+

CPU Utilization in the Fabric

+
let data =`analytics:/Devices/*/versioned-data/hardware/disk/\/mnt\/flash`
+let data = data | map(merge(_value) | fields("usedPartitionPercent"))
+
+let test = 0
+let i60 = 0
+let b6080 = 0
+let s80 = 0
+
+for device, deviceData in data {
+    if dictHasKey(deviceData, "usedPartitionPercent") {
+        let test = deviceData["usedPartitionPercent"]
+        if test < 60 {
+            let i60=i60+1
+        }
+        if test < 80 && test > 60 {
+            let b6080=b6080+1
+        }
+        if test > 80 {
+            let s80=s80+1
+        }
+    }
+}
+
+let usageDict = newDict()
+
+usageDict["Disk < 60%"] = i60
+usageDict["60% < Disk < 80%"] = b6080
+usageDict["Disk > 80%"] = s80
+
+usageDict
+
+
+
+
+

Memory Usage in the Fabric

+
let data =`analytics:/Devices/*/versioned-data/hardware/meminfo/memoryUsage`
+let data = data | map(merge(_value) | fields("usedMemoryPercent"))
+
+let test = 0
+let i60 = 0
+let b6070 = 0
+let s70 = 0
+
+for device, deviceData in data {
+    if dictHasKey(deviceData, "usedMemoryPercent") {
+        let test = deviceData["usedMemoryPercent"]
+        if test < 70 {
+            let i60=i60+1
+        }
+        if test < 80 && test > 70 {
+            let b6070=b6070+1
+        }
+        if test > 70 {
+            let s70=s70+1
+        }
+    }
+}
+
+let usageDict = newDict()
+
+usageDict["Memory < 60%"] = i60
+usageDict["60% < Memory < 70%"] = b6070
+usageDict["Memory > 70%"] = s70
+
+usageDict
+
+
+
+
+

/mnt/flash usage

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/<_SWITCH_ROLE>/elements`)
+
+let deviceDisks = `analytics:/Devices/*/versioned-data/hardware/disk/\/mnt\/flash`
+
+if str(_POD_NAME) == "" && str(_SWITCH_ROLE) == "" {
+    let data = deviceDisks
+} else {
+    if str(_SWITCH_ROLE) == "" {
+        let data = deviceDisks | where(strContains(str(devices), _key))
+    } else {
+        if str(_POD_NAME) == "" {
+            let data = deviceDisks | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        } else {
+            let data = deviceDisks | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        }
+    }
+}
+
+data | map(merge(_value) | fields("usedPartitionPercent"))
+
+
+system_health_check1 +system_health_check2 +

Download the Dashboard JSON here

+
+
+
+

TCAM Capacity

+
+

7280R2 Switches

+
# Get the devices that have the egw (EVPN gateway) tag
+let devices = merge(`analytics:/tags/labels/devices/switch_role/value/egw/elements`)
+
+# Get the L3 hardware capacity for all devices
+let hwCapL3 =`*:/Sysdb/hardware/capacity/status/l3/entry`
+
+# Get the devices that are part of a specific pod
+let podDeviceList = merge(`analytics:/tags/labels/devices/pod_name/value/<_pod_name>/elements`)
+
+# If the POD_NAME variable is not set (none) show the utilization for all pods
+if str(_pod_name) == "" {
+    let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+} else {
+    let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(podDeviceList, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+}
+for deviceKey, deviceValue in filteredHwCapL3{
+    filteredHwCapL3[deviceKey]["MAC"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"LEM\", \"feature\": \"MAC\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"LEM\", \"feature\": \"MAC\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["FEC Routing"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"FEC\", \"feature\": \"Routing\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"FEC\", \"feature\": \"Routing\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["Routing1"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource1\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource1\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["Routing2"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource2\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource2\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["Routing3"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource3\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource3\"}")]["maxLimit"]*100
+}
+filteredHwCapL3 | map(_value | fields("MAC","FEC Routing", "Routing1", "Routing2", "Routing3"))
+
+
+
+
+

7050X3 Switches

+
# Get the devices that have the `hdl` (high-density leaf) tag
+let devices = merge(`analytics:/tags/labels/devices/switch_role/value/hdl/elements`)
+
+# Get the L3 hardware capacity for all devices
+let hwCapL3 =`*:/Sysdb/hardware/capacity/status/l3/entry`
+
+# Get the L2 hardware capacity for all devices
+let hwCapL2 =`*:/Sysdb/hardware/capacity/status/l2/entry`
+
+# Get the devices that are part of a specific pod
+let podDeviceList = merge(`analytics:/tags/labels/devices/pod_name/value/<_pod_name>/elements`)
+
+# If the POD_NAME variable is not set (none) show the utilization for all pods
+if str(_pod_name) == "" {
+    let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+    let filteredHwCapL2 = hwCapL2 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+} else {
+    let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(podDeviceList, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+    let filteredHwCapL2 = hwCapL2 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(podDeviceList, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+}
+
+# Build a new table with MAC, HOST, LPM and NextHop utilization
+for deviceKey, deviceValue in filteredHwCapL3{
+    filteredHwCapL3[deviceKey]["Host Percent"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"Host\", \"feature\": \"\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"Host\", \"feature\": \"\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["LPM Percent"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"LPM\", \"feature\": \"\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"LPM\", \"feature\": \"\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["NextHop Percent"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"NextHop\", \"feature\": \"\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"NextHop\", \"feature\": \"\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["MAC Percent"] = filteredHwCapL2[deviceKey][complexKey("{\"chip\": \"Linecard0/0\", \"table\":\"MAC\", \"feature\": \"L2\"}")]["used"] / filteredHwCapL2[deviceKey][complexKey("{\"chip\": \"Linecard0/0\", \"table\":\"MAC\", \"feature\": \"L2\"}")]["maxLimit"]*100
+}
+filteredHwCapL3 | map(_value | fields("MAC Percent","Host Percent", "LPM Percent", "NextHop Percent"))
+
+
+
+
+

7020R Switches

+
# Get the devices that have the `ldl` (low-density leaf) tag
+let devices = merge(`analytics:/tags/labels/devices/switch_role/value/ldl/elements`)
+
+# Get the L3 hardware capacity for all devices
+let hwCapL3 =`*:/Sysdb/hardware/capacity/status/l3/entry`
+
+# Get the multicast hardware capacity for all devices
+let hwCapMcast = `*:/Sysdb/hardware/capacity/status/mcast/entry`
+
+# Get the devices that are part of a specific pod
+let podDeviceList = merge(`analytics:/tags/labels/devices/pod_name/value/<_pod_name>/elements`)
+let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(podDeviceList, complexKey("{\"deviceID\": \""+_key+"\"}")))| map(merge(_value) )
+let filteredHwCapMcast = hwCapMcast | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(podDeviceList, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+
+# Build a new table with MAC, MCDB, FEC Routing, Routing1, Routing2 and Routing3 utilization
+for deviceKey, deviceValue in filteredHwCapL3{
+    filteredHwCapL3[deviceKey]["MAC"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"LEM\", \"feature\": \"MAC\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"LEM\", \"feature\": \"MAC\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["FEC Routing"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"FEC\", \"feature\": \"Routing\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"FEC\", \"feature\": \"Routing\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["Routing1"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource1\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource1\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["Routing2"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource2\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource2\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["Routing3"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource3\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource3\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["MCDB"] = filteredHwCapMcast[deviceKey][complexKey("{\"chip\": \"Jericho0\", \"table\":\"MCDB\", \"feature\": \"\"}")]["used"] / filteredHwCapMcast[deviceKey][complexKey("{\"chip\": \"Jericho0\", \"table\":\"MCDB\", \"feature\": \"\"}")]["maxLimit"]*100
+}
+
+filteredHwCapL3 | map(_value | fields("MAC","MCDB","FEC Routing", "Routing1", "Routing2", "Routing3"))
+
+
+TCAM Capacity +

Download the Dashboard JSON here

+
+
+
+

Listing Devices that have a file in /var/core

+

> Useful to understand if there were any agent crashes on EOS

+
let devices = `analytics:/Devices/*/versioned-data/Device` | map(merge(_value)["hostname"])
+let result = newDict()
+let deviceDiskStats = `*:/Kernel/vfs/stat/\/var\/core` | map(merge(_value))
+let deviceDiskStats = deviceDiskStats | where(dictHasKey(_value, "blocks") && dictHasKey(_value, "bfree") && _value["blocks"] != _value["bfree"])
+for device, diskStat in deviceDiskStats {
+    result[devices[device]] = newDict() | setFields("value", true)
+}
+result
+
+
+varcore files +

Download the Dashboard JSON here

+
+
+

List the serial numbers for all transceivers

+
let xcvrStatus = `*:/Sysdb/hardware/archer/xcvr/status/all/*`
+let filteredXcvrStat = xcvrStatus | map(_value | mapne(_value, _value | field("goVendorInfo") | field("vendorSn")))
+filteredXcvrStat | map(_value | mapne(_value, _value[0]))
+
+
+List the serial numbers for all transceivers +

Download the Dashboard JSON here

+
+
+

List the transceiver serial numbers that match the input regex

+
let data = `*:/Sysdb/hardware/archer/xcvr/status/all/*`
+let xcvrStatus = data | map(_value | mapne(_value, _value | field("goVendorInfo") | field("vendorSn")))
+let xcvrStatus = xcvrStatus | map(_value | mapne(_value[0], _value))
+
+xcvrStatus | map(_value | where(reMatch(_value, _regexInput))) | where(length(_value) > 0)
+
+
+List the transceiver serial numbers that match the input regex +

Download the Dashboard JSON here

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/system_health/ntp_stats.html b/examples/system_health/ntp_stats.html new file mode 100644 index 0000000..12ca7d5 --- /dev/null +++ b/examples/system_health/ntp_stats.html @@ -0,0 +1,124 @@ + + + + + + + NTP Stats — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

NTP Stats

+
let ntpData = `*:/NTP/status/system/variables` | map(merge(_value))
+let output = newDict()
+for devID,devData in ntpData{
+    output[devID] = newDict()
+    output[devID]["peer"]= devData["refid"]
+    output[devID]["Stratum"]=devData["stratum"]
+    output[devID]["OffSet"]=devData["offset"]
+    }
+output
+
+
+NTP Stats +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/system_health/output_power_over_48h.html b/examples/system_health/output_power_over_48h.html new file mode 100644 index 0000000..8a83c8f --- /dev/null +++ b/examples/system_health/output_power_over_48h.html @@ -0,0 +1,126 @@ + + + + + + + PowerSupply Output — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

PowerSupply Output

+
let powerSupply = `analytics:/Devices/*/versioned-data/environment/power/aggregate/*/out/15m`[48h]
+let result = newDict()
+for key, value in _device{
+
+    if dictHasKey(powerSupply,key) {
+         for psk,psv in powerSupply[key]{
+             result[key+"-"+psk] = powerSupply[key][psk] | field("value") | field("avg")
+         }
+    }
+}
+result
+
+
+PowerSupply Output +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/system_health/system_health_check.html b/examples/system_health/system_health_check.html new file mode 100644 index 0000000..764ce23 --- /dev/null +++ b/examples/system_health/system_health_check.html @@ -0,0 +1,209 @@ + + + + + + + System Health Check — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

System Health Check

+
+

CPU Utilization in the Fabric

+
let data =`analytics:/Devices/*/versioned-data/hardware/disk/\/mnt\/flash`
+let data = data | map(merge(_value) | fields("usedPartitionPercent"))
+
+let test = 0
+let i60 = 0
+let b6080 = 0
+let s80 = 0
+
+for device, deviceData in data {
+    if dictHasKey(deviceData, "usedPartitionPercent") {
+        let test = deviceData["usedPartitionPercent"]
+        if test < 60 {
+            let i60=i60+1
+        }
+        if test < 80 && test > 60 {
+            let b6080=b6080+1
+        }
+        if test > 80 {
+            let s80=s80+1
+        }
+    }
+}
+
+let usageDict = newDict()
+
+usageDict["Disk < 60%"] = i60
+usageDict["60% < Disk < 80%"] = b6080
+usageDict["Disk > 80%"] = s80
+
+usageDict
+
+
+
+
+

Memory Usage in the Fabric

+
let data =`analytics:/Devices/*/versioned-data/hardware/meminfo/memoryUsage`
+let data = data | map(merge(_value) | fields("usedMemoryPercent"))
+
+let test = 0
+let i60 = 0
+let b6070 = 0
+let s70 = 0
+
+for device, deviceData in data {
+    if dictHasKey(deviceData, "usedMemoryPercent") {
+        let test = deviceData["usedMemoryPercent"]
+        if test < 70 {
+            let i60=i60+1
+        }
+        if test < 80 && test > 70 {
+            let b6070=b6070+1
+        }
+        if test > 70 {
+            let s70=s70+1
+        }
+    }
+}
+
+let usageDict = newDict()
+
+usageDict["Memory < 60%"] = i60
+usageDict["60% < Memory < 70%"] = b6070
+usageDict["Memory > 70%"] = s70
+
+usageDict
+
+
+
+
+

/mnt/flash usage

+
let devices = merge(`analytics:/tags/labels/devices/pod_name/value/<_POD_NAME>/elements`)
+let devicesSwitchLabel = merge(`analytics:/tags/labels/devices/switch_role/value/<_SWITCH_ROLE>/elements`)
+
+let deviceDisks = `analytics:/Devices/*/versioned-data/hardware/disk/\/mnt\/flash`
+
+if str(_POD_NAME) == "" && str(_SWITCH_ROLE) == "" {
+    let data = deviceDisks
+} else {
+    if str(_SWITCH_ROLE) == "" {
+        let data = deviceDisks | where(strContains(str(devices), _key))
+    } else {
+        if str(_POD_NAME) == "" {
+            let data = deviceDisks | where(dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        } else {
+            let data = deviceDisks | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(devicesSwitchLabel, complexKey("{\"deviceID\": \""+_key+"\"}")))
+        }
+    }
+}
+
+data | map(merge(_value) | fields("usedPartitionPercent"))
+
+
+system_health_check1 +system_health_check2 +

Download the Dashboard JSON here

+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/system_health/tcam_capacity.html b/examples/system_health/tcam_capacity.html new file mode 100644 index 0000000..f959037 --- /dev/null +++ b/examples/system_health/tcam_capacity.html @@ -0,0 +1,205 @@ + + + + + + + TCAM Capacity — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

TCAM Capacity

+
+

7280R2 Switches

+
# Get the devices that have the egw (EVPN gateway) tag
+let devices = merge(`analytics:/tags/labels/devices/switch_role/value/egw/elements`)
+
+# Get the L3 hardware capacity for all devices
+let hwCapL3 =`*:/Sysdb/hardware/capacity/status/l3/entry`
+
+# Get the devices that are part of a specific pod
+let podDeviceList = merge(`analytics:/tags/labels/devices/pod_name/value/<_pod_name>/elements`)
+
+# If the POD_NAME variable is not set (none) show the utilization for all pods
+if str(_pod_name) == "" {
+    let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+} else {
+    let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(podDeviceList, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+}
+for deviceKey, deviceValue in filteredHwCapL3{
+    filteredHwCapL3[deviceKey]["MAC"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"LEM\", \"feature\": \"MAC\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"LEM\", \"feature\": \"MAC\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["FEC Routing"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"FEC\", \"feature\": \"Routing\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"FEC\", \"feature\": \"Routing\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["Routing1"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource1\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource1\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["Routing2"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource2\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource2\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["Routing3"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource3\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource3\"}")]["maxLimit"]*100
+}
+filteredHwCapL3 | map(_value | fields("MAC","FEC Routing", "Routing1", "Routing2", "Routing3"))
+
+
+
+
+

7050X3 Switches

+
# Get the devices that have the `hdl` (high-density leaf) tag
+let devices = merge(`analytics:/tags/labels/devices/switch_role/value/hdl/elements`)
+
+# Get the L3 hardware capacity for all devices
+let hwCapL3 =`*:/Sysdb/hardware/capacity/status/l3/entry`
+
+# Get the L2 hardware capacity for all devices
+let hwCapL2 =`*:/Sysdb/hardware/capacity/status/l2/entry`
+
+# Get the devices that are part of a specific pod
+let podDeviceList = merge(`analytics:/tags/labels/devices/pod_name/value/<_pod_name>/elements`)
+
+# If the POD_NAME variable is not set (none) show the utilization for all pods
+if str(_pod_name) == "" {
+    let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+    let filteredHwCapL2 = hwCapL2 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+} else {
+    let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(podDeviceList, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+    let filteredHwCapL2 = hwCapL2 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(podDeviceList, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+}
+
+# Build a new table with MAC, HOST, LPM and NextHop utilization
+for deviceKey, deviceValue in filteredHwCapL3{
+    filteredHwCapL3[deviceKey]["Host Percent"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"Host\", \"feature\": \"\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"Host\", \"feature\": \"\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["LPM Percent"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"LPM\", \"feature\": \"\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"LPM\", \"feature\": \"\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["NextHop Percent"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"NextHop\", \"feature\": \"\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"NextHop\", \"feature\": \"\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["MAC Percent"] = filteredHwCapL2[deviceKey][complexKey("{\"chip\": \"Linecard0/0\", \"table\":\"MAC\", \"feature\": \"L2\"}")]["used"] / filteredHwCapL2[deviceKey][complexKey("{\"chip\": \"Linecard0/0\", \"table\":\"MAC\", \"feature\": \"L2\"}")]["maxLimit"]*100
+}
+filteredHwCapL3 | map(_value | fields("MAC Percent","Host Percent", "LPM Percent", "NextHop Percent"))
+
+
+
+
+

7020R Switches

+
# Get the devices that have the `ldl` (low-density leaf) tag
+let devices = merge(`analytics:/tags/labels/devices/switch_role/value/ldl/elements`)
+
+# Get the L3 hardware capacity for all devices
+let hwCapL3 =`*:/Sysdb/hardware/capacity/status/l3/entry`
+
+# Get the multicast hardware capacity for all devices
+let hwCapMcast = `*:/Sysdb/hardware/capacity/status/mcast/entry`
+
+# Get the devices that are part of a specific pod
+let podDeviceList = merge(`analytics:/tags/labels/devices/pod_name/value/<_pod_name>/elements`)
+let filteredHwCapL3 = hwCapL3 | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(podDeviceList, complexKey("{\"deviceID\": \""+_key+"\"}")))| map(merge(_value) )
+let filteredHwCapMcast = hwCapMcast | where(dictHasKey(devices, complexKey("{\"deviceID\": \""+_key+"\"}")) && dictHasKey(podDeviceList, complexKey("{\"deviceID\": \""+_key+"\"}"))) | map(merge(_value) )
+
+# Build a new table with MAC, MCDB, FEC Routing, Routing1, Routing2 and Routing3 utilization
+for deviceKey, deviceValue in filteredHwCapL3{
+    filteredHwCapL3[deviceKey]["MAC"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"LEM\", \"feature\": \"MAC\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"LEM\", \"feature\": \"MAC\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["FEC Routing"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"FEC\", \"feature\": \"Routing\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"\", \"table\":\"FEC\", \"feature\": \"Routing\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["Routing1"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource1\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource1\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["Routing2"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource2\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource2\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["Routing3"] = filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource3\"}")]["used"] / filteredHwCapL3[deviceKey][complexKey("{\"chip\": \"Jericho\", \"table\":\"Routing\", \"feature\": \"Resource3\"}")]["maxLimit"]*100
+    filteredHwCapL3[deviceKey]["MCDB"] = filteredHwCapMcast[deviceKey][complexKey("{\"chip\": \"Jericho0\", \"table\":\"MCDB\", \"feature\": \"\"}")]["used"] / filteredHwCapMcast[deviceKey][complexKey("{\"chip\": \"Jericho0\", \"table\":\"MCDB\", \"feature\": \"\"}")]["maxLimit"]*100
+}
+
+filteredHwCapL3 | map(_value | fields("MAC","MCDB","FEC Routing", "Routing1", "Routing2", "Routing3"))
+
+
+TCAM Capacity +

Download the Dashboard JSON here

+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/system_health/varcore.html b/examples/system_health/varcore.html new file mode 100644 index 0000000..c0ba0c8 --- /dev/null +++ b/examples/system_health/varcore.html @@ -0,0 +1,124 @@ + + + + + + + Listing Devices that have a file in /var/core — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Listing Devices that have a file in /var/core

+

> Useful to understand if there were any agent crashes on EOS

+
let devices = `analytics:/Devices/*/versioned-data/Device` | map(merge(_value)["hostname"])
+let result = newDict()
+let deviceDiskStats = `*:/Kernel/vfs/stat/\/var\/core` | map(merge(_value))
+let deviceDiskStats = deviceDiskStats | where(dictHasKey(_value, "blocks") && dictHasKey(_value, "bfree") && _value["blocks"] != _value["bfree"])
+for device, diskStat in deviceDiskStats {
+    result[devices[device]] = newDict() | setFields("value", true)
+}
+result
+
+
+varcore files +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/system_health/xcvr_sn_list.html b/examples/system_health/xcvr_sn_list.html new file mode 100644 index 0000000..8f5b355 --- /dev/null +++ b/examples/system_health/xcvr_sn_list.html @@ -0,0 +1,118 @@ + + + + + + + List the serial numbers for all transceivers — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

List the serial numbers for all transceivers

+
let xcvrStatus = `*:/Sysdb/hardware/archer/xcvr/status/all/*`
+let filteredXcvrStat = xcvrStatus | map(_value | mapne(_value, _value | field("goVendorInfo") | field("vendorSn")))
+filteredXcvrStat | map(_value | mapne(_value, _value[0]))
+
+
+List the serial numbers for all transceivers +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/system_health/xcvr_sn_list_filtered.html b/examples/system_health/xcvr_sn_list_filtered.html new file mode 100644 index 0000000..17b11d4 --- /dev/null +++ b/examples/system_health/xcvr_sn_list_filtered.html @@ -0,0 +1,120 @@ + + + + + + + List the transceiver serial numbers that match the input regex — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • + View page source +
  • +
+
+
+
+
+ +
+

List the transceiver serial numbers that match the input regex

+
let data = `*:/Sysdb/hardware/archer/xcvr/status/all/*`
+let xcvrStatus = data | map(_value | mapne(_value, _value | field("goVendorInfo") | field("vendorSn")))
+let xcvrStatus = xcvrStatus | map(_value | mapne(_value[0], _value))
+
+xcvrStatus | map(_value | where(reMatch(_value, _regexInput))) | where(length(_value) > 0)
+
+
+List the transceiver serial numbers that match the input regex +

Download the Dashboard JSON here

+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/genindex.html b/genindex.html new file mode 100644 index 0000000..56b1e66 --- /dev/null +++ b/genindex.html @@ -0,0 +1,112 @@ + + + + + + Index — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ +
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..5e6a435 --- /dev/null +++ b/index.html @@ -0,0 +1,162 @@ + + + + + + + AQL Documentation — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

AQL Documentation

+

This page is the official documentation for the CloudVision Advanced Query Language (AQL).

+

The AQL language can be used with Arista’s CloudVision to configure dashboards, query data, configure custom event rules, etc.

+
+

Warning

+

AQL scripts and dashboards may stop working and require a manual update following a hardware upgrade or EOS image update, as data paths may have changed.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/index_doc.html b/index_doc.html new file mode 100644 index 0000000..a8c72b7 --- /dev/null +++ b/index_doc.html @@ -0,0 +1,1683 @@ + + + + + + + Language Specification — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Language Specification

+ +
+

Quick overview

+

The syntax in AQL is similar to that of scripting languages. However, it is designed to be used like +a query language. Its types are specifically designed to match the data structures of the CloudVision +database.

+

In interactive mode, each statement is executed independently, and therefore each of their values is +printed when run. However, when running an AQL script with multiple statements from the CloudVision +dashboard or from a file, the only displayed value is the value of the script as a whole, which is the +value of the last statement in the script.

+

Example in interactive mode:

+
>>> let a = 1
+>>> a
+1
+>>> a + 2
+3
+
+
+

Running it in one go:

+
let a = 1
+a
+a+2
+
+
+

This script simply returns 3.

+

The main features of the language are:

+
    +
  • Queries: quickly fetch data from the CloudVision database

  • +
  • Filters: efficiently format and extract the appropriate data from the query results

  • +
+

Example:

+
# This first statement performs a query to get aggregate data for each interface of device SSJ123456
+let hardwareAggs = `analytics:/Devices/SSJ123456/versioned-data/interfaces/data/*/aggregate/hardware/xcvr/1m`
+
+# This statement uses filters to extract the average temperature for each interface
+let temperaturePerIntf = hardwareAggs | map((_value | field("temperature") | field("avg"))[0])
+
+# This last statement computes the average temperature across all the interfaces in the device and is the
+# return value of the script
+mean(temperaturePerIntf)
+
+
+
+
+

Basic statements

+

Statements are separated by new lines. +Comments start with a # so anything following # is not part of a statement.

+
>>> 1 + 2
+3
+>>> 1 * (2+1) / (2*5)
+0.3
+
+
+

Values can be assigned into a variable using the let keyword.

+
>>> let myVar1 = 9 % 5 # variable myVar1 contains 4
+
+
+

The last statement’s value is stored in the metavariable _. However, some statements like +variable assignments do not have a value, so they don’t change the value of _.

+
>>> _
+0.3
+
+
+

Variables can be used in expressions.

+
>>> 5 * _ + 12.4 + myVar1 + -1 - 1
+15.9
+>>> _ + 1
+16.9
+
+
+

Variable names must begin with a letter (lower or uppercase). The rest of the name can contain letters +(lower or uppercase), digits, and underscores.

+
+

Warning

+

Variable names that are prefixed with an underscore are metavariables and are set by the interpreter. +These cannot be reassigned (read-only) but they can be used.

+

More details about metavariables defined by named wildcards (<wcName>) in the Named wildcards +section of this document.

+
+
>>> let myVar_Name2 = 1
+>>> myVar_Name2
+1
+>>> let someData = `<d>:/Devices/some/data/path`
+>>> _d
+JPE123456
+>>> let _metavar = 12
+error: input:1:1: illegal variable name: _metavar
+
+
+
+
+

Types

+
+

num

+

The num type is a float64 and is the only numerical type. AQL does not have a native integer type.

+

This type can be defined through literals, either an integer or a floating-point value:

+
>>> let i = 12
+>>> let j = 15.42
+>>> i+j
+27.42
+
+
+
+
+

bool

+

The bool type is a boolean and can either be true or false.

+

The syntax of its literal is either the true or false keyword.

+
>>> let b = true
+>>> b && false
+false
+
+
+
+
+

str

+

The str type is a string of characters.

+

The syntax of its literal is any string of character surrounded with double-quotes. To insert a double-quote +within the literal, it can be prefixed with a backslash \\. +Single-quote strings are not supported in AQL.

+

All types can be cast to str.

+
>>> "Don't \"panic\"!"
+Don't "panic"!
+>>> str(12.0) # this is a cast from num to str
+12
+>>> str(12.1)
+12.1
+
+
+
+
+

time

+

The time type holds a timestamp. It is the key type in the timeseries type returned by queries.

+

There are no literals for time, but it can be cast from a str following the syntax described in RFC 3339.

+
>>> let t = time("2006-01-02T15:04:05+07:00")
+>>> t
+2006-01-02 15:04:05 +0700 +0700
+>>> t + 15s
+2006-01-02 15:04:20 +0700 +0700
+
+
+
+
+

duration

+

The duration type defines a time interval. It can be used to define a time range of data to get +in queries, and it can be added to or subtracted from time values.

+

The syntax of its literal is a signed (or not) sequence of decimal numbers followed by a unit suffix. +It can also be composed of multiple values in different time units: 300ms, -1.5h, 2h45m.

+

Valid time units are:

+
    +
  • ns (nanosecond)

  • +
  • us (microsecond)

  • +
  • ms (millisecond)

  • +
  • s (second)

  • +
  • m (minute)

  • +
  • h (hour)

  • +
+
>>> 5h30ms
+5h0m0.03s
+>>> 7 * 24h # week
+168h0m0s
+>>> time("2006-01-02T15:04:05+07:00") + 5h15s
+2006-01-02 15:04:20 +0700 +0700
+
+
+
+
+

type

+

The type type holds type information. Any value can be cast to type to know its type.

+

The syntax of its literal is any type name without any quotes or delimiter.

+
>>> let a = 2
+>>> type(a) # This is a cast to type `type`
+num
+>>> type("Hello World!")
+str
+>>> type(str)
+type
+>>> type("Don't panic!") == bool
+false
+
+
+
+
+

timeseries

+

The timeseries type is a list of values (of any type), indexed by timestamps (time values). +Its values can be accessed either by num index or time index. If there is no exact match for the +specified time, accessing its value will return the latest entry before that time.

+
+

Note

+

There are no literals for timeseries and they cannot be manually created. It can be returned by some +functions (see the documentation for Standard Library functions), and all AQL queries return a timeseries +(which can be contained in a dict, see sections about Wildcards).

+
+
>>> let a = `analytics:/Devices/JPE17191574/versioned-data/interfaces/data/Ethernet50/aggregate/hardware/xcvr/1m`[5m] | field("temperature") | field("avg")
+>>> a
+timeseries{
+    start: 2021-10-26 14:32:17.167535 +0100 IST
+    end: 2021-10-26 14:37:17.167535 +0100 IST
+    2021-10-26 14:32:00 +0100 IST: 26.77301344308594
+    2021-10-26 14:33:00 +0100 IST: 26.78515625
+    2021-10-26 14:34:00 +0100 IST: 26.64152704258496
+    2021-10-26 14:35:00 +0100 IST: 26.68106989897461
+    2021-10-26 14:36:00 +0100 IST: 26.76746009308496
+    2021-10-26 14:37:00 +0100 IST: 26.78515625
+}
+>>> a[0]
+26.77301344308594
+>>> a[time("2021-10-26T14:34:05+01:00")]
+26.64152704258496
+
+
+
+
+

dict

+

The dict type is a collection of key/value pairs (map).

+
+

Note

+

There are no literals for dict but an empty dict can be created using the newDict function, and +its fields can be set using the bracket operator assignments or various filters such as setFields.

+
+
>>> let d = newDict()
+>>> d
+dict{}
+>>> d["key1"] = 1
+>>> d["key2"] = 2
+>>> d
+dict{
+    key1: 1
+    key2: 2
+}
+>>> d | setFields("key2", 0, "key3", 3)
+dict{
+    key1: 1
+    key2: 0
+    key3: 3
+}
+
+
+
+
+

unknown

+

The unknown type is applied to any value that is not a standard AQL type. Some of the data in CloudVision +can be of a type that does not match any of the native AQL types. There is limited support +to extract and use these values (they can be used in dict keys and values).

+
+

Note

+

There are no literals but some values of that type can be created using the complexKey function. +See sections Complex path elements and complexKey.

+
+
+
+
+

Language keywords

+

Here is a full list of the language keywords in AQL:

+ +
+
+

Language Operators

+

From lowest to highest precedence:

+
    +
  • = (assignment)

  • +
  • ? : (ternary operations)

  • +
  • || (logical OR)

  • +
  • && (logical AND)

  • +
  • != == (equality check operators)

  • +
  • < <= >= > (comparison operators)

  • +
  • + - (addition/concatenation and subtraction)

  • +
  • * / % (multiplication, division, modulo)

  • +
  • | (pipe for filters)

  • +
  • ^ (power)

  • +
  • ! (logical NOT)

  • +
  • [] (access values at a specific index/key/time in timeseries/dicts)

  • +
+
+
+

Comparisons

+

Two values of the same type can be compared.

+

Equality operators (== and !=) work with values of any type, even dict and timeseries (but +both values must be of the same type)

+
>>> true == false
+false
+>>> let s = "myString"
+>>> "myString" == s
+true
+>>> 2 != 3
+true
+
+
+

Values can also be compared using the greater and lower operators (<, <=, >, >=). Both compared +values must have the same type, either str (ASCII order), num, time (before or after), or duration.

+
>>> myVar1
+4
+>>> myVar1 > 4
+false
+>>> myVar1 >= 4
+true
+myVar1 == 4
+true
+>>> let myBooleanVar = myVar1 + 1 <= 5
+>>> myBooleanVar
+true
+>>> "ab" < "ac"
+true
+
+
+
+
+

Operations

+
+

Boolean operations

+

Boolean values can be used with ! (NOT), && (AND), and || (OR) for boolean logic

+
>>>  myBooleanVar
+true
+>>>  myBooleanVar && 1 > 2
+false
+>>> !(myBooleanVar && 1 > 2) && !_ || 1 > 2
+true
+
+
+
+
+

String concatenations

+

Strings can be concatenated with the + operator.

+
>>> "Hello " + "world" + "!"
+Hello world!
+
+
+
+
+

Additions and Subtractions

+

Additions (+) and subtractions (-) can be performed with the following type combinations:

+
    +
  • num + num: returns a num

  • +
  • num - num: returns a num

  • +
  • time - time: returns a duration

  • +
  • time + duration: returns a time

  • +
  • time - duration: returns a time

  • +
+
>>> 2+3.4
+5.4
+>>> let n = now() # now() returns the current time as a `time` value
+>>> n
+2021-10-26 15:19:56.184361 +0100
+>>> let n2 = n - 15m
+>>> n2
+2021-10-26 15:04:56.184361 +0100
+>>> n - n2
+15m0s
+>>> n2 + 15*60s == n
+true
+
+
+
+
+

Multiplications and Divisions

+

Multiplications (*) and divisions (/) can be performed with the following type combinations:

+
    +
  • num * num: returns a num

  • +
  • num / num: returns a num

  • +
  • num * duration: returns a duration

  • +
  • duration / num: returns a duration

  • +
+
>>> 3*3
+9
+>>> 4.4/4
+1.1
+>>> 3*60s
+3m0s
+>>> 3m/180
+1s
+
+
+
+
+

Modulo

+

The modulo (%) operator returns the remainder of a division. It can only be used with two num values.

+
>>> 10 % 3
+1
+
+
+
+
+

Power

+

The power (^) operator returns a to the power of b. It can only be used with two num values.

+
>>> 3^3
+27
+
+
+
+
+
+

Typecasts

+

It is possible to cast values of a certain type to another using the syntax typename(valueToCast). +Here is a typecast table defining which types can be cast to which other types.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

FROM / TO

num

bool

str

time

duration

type

timeseries

dict

unknown

num

YES

YES

YES

YES

YES

YES

NO

NO

NO

bool

YES

YES

YES

NO

NO

YES

NO

NO

NO

str

YES

YES

YES

YES

YES

YES

NO

NO

NO

time

YES

YES

YES

YES

NO

YES

NO

NO

NO

duration

YES

NO

YES

NO

YES

YES

NO

NO

NO

type

NO

NO

YES

NO

NO

YES

NO

NO

NO

timeseries

NO

NO

YES

NO

NO

YES

YES

NO

NO

dict

NO

NO

YES

NO

NO

YES

NO

YES

NO

unknown

NO

NO

YES

NO

NO

YES

NO

NO

NO

+
+

Note

+
    +
  • For all casts between num, duration, and time, the time unit is the nanosecond

  • +
  • Cast from str to num supports float and integer notation but also scientific (1e+2, 15e-3 etc.)

  • +
  • Casts between time and str follow the syntax defined in +RFC 3339.

  • +
+
+
>>> num("12")
+12
+>>> str(11+1) + "a"
+12a
+>>> type("42")
+str
+>>> type(type("42"))
+type
+
+
+

As described in the section about type type, type names can be used as type literals to perform +type-assertions.

+
>>> type(false) == str
+false
+>>> type(false) == bool
+true
+
+
+
+
+

Queries

+

AQL can fetch data from the CloudVision database by using queries. The general syntax is the following:

+
`datasetType/datasetName:/path/to/data`[queryParameter]
+
+
+
+

Dataset

+

The dataset section of the query is split into two parts with a forward slash (/). The first part +is the dataset type (e.g. device, app, config…).

+

The second part is the dataset name.

+

Example:

+
`user/johndoe:/path/to/data`[queryParameter]
+
+
+

If unspecified, the dataset type will default to device:

+
`JPE123456:/path/to/data`[queryParameter]
+
+
+
+
+

Path

+

The path section of the query is the path to the data in the CloudVision database, and each path +element is separated by a forward slash (/).

+
`analytics:/Devices/JPE123456/versioned-data/interfaces/data/Ethernet1/rates`[queryParameter]
+
+
+

A query with a fully specified path like the above will always return a timeseries.

+

The value associated with each specific time in the timeseries is a dict containing all the +key-value pairs updated at that path, at that specific time.

+
+
+

Wildcards

+

If a path element or the dataset name (dataset type can not be wildcarded) is replaced with a simple +star sign (*), called a wildcard, the query fetches the data at all the paths matching this wildcarded path.

+

Example: In the previous section, the query was fetching the interface rates for device “JPE123456”, +and interface “Ethernet1”. This example gets the interface rates for all interfaces of device “JPE123456”.

+
`analytics:/Devices/JPE123456/versioned-data/interfaces/data/*/rates`[queryParameter]
+
+
+

Queries containing a wildcard do not return a timeseries, but a dict. Its keys are the path +element values matching the wildcard (in the example above, the interface names). The dict values +are the timeseries that would have been returned if querying the same path with the wildcard +replaced with each possible key.

+
>>> `analytics:/Devices/JPE123456/versioned-data/interfaces/data/Ethernet1/rates`[0]
+timeseries{
+    start: 2021-10-26 16:12:46.870252166 +0100 IST
+    end: 2021-10-26 16:12:47.674314 +0100 IST
+    2021-10-26 16:12:46.870252166 +0100 IST: dict{
+        inMulticastPkts: 0.5000081856910986
+        inOctets: 61.50100684000513
+        outMulticastPkts: 0
+        outOctets: 0
+    }
+}
+>>> `analytics:/Devices/JPE123456/versioned-data/interfaces/data/*/rates`[0]
+dict{
+    Ethernet1: timeseries{
+        start: 2021-10-26 16:13:16.870363498 +0100 IST
+        end: 2021-10-26 16:13:34.615865 +0100 IST
+        2021-10-26 16:13:16.870363498 +0100 IST: dict{
+            outMulticastPkts: 0
+            outOctets: 0
+        }
+        2021-10-26 16:13:26.870256382 +0100 IST: dict{
+            inMulticastPkts: 0.5000009149562546
+            inOctets: 61.50011253961932
+        }
+    }
+    Ethernet2: timeseries{
+        start: 2021-10-26 16:13:26.870256382 +0100 IST
+        end: 2021-10-26 16:13:34.615865 +0100 IST
+        2021-10-26 16:13:26.870256382 +0100 IST: dict{
+            inMulticastPkts: 0.10000018299125094
+            inOctets: 38.50007045163161
+            inUcastPkts: 0.20000036598250187
+            outMulticastPkts: 0.10000018299125094
+            outOctets: 12.80002342288012
+        }
+    }
+
+
+

A query can also contain multiple wildcards, which will result in several levels of nested dicts, +with timeseries at the bottom level.

+

This example fetches the same data as before, but for all interfaces of all devices, using two +wildcards:

+
>>> `analytics:/Devices/*/versioned-data/interfaces/data/*/rates`[0]
+dict{
+    JPE123456: dict{
+        Ethernet1: timeseries{
+            start: 2021-10-26 16:13:16.870363498 +0100 IST
+            end: 2021-10-26 16:13:34.615865 +0100 IST
+            2021-10-26 16:13:16.870363498 +0100 IST: dict{
+                outMulticastPkts: 0
+                outOctets: 0
+            }
+            2021-10-26 16:13:26.870256382 +0100 IST: dict{
+                inMulticastPkts: 0.5000009149562546
+                inOctets: 61.50011253961932
+            }
+        }
+        Ethernet2: timeseries{
+            start: 2021-10-26 16:13:26.870256382 +0100 IST
+            end: 2021-10-26 16:13:34.615865 +0100 IST
+            2021-10-26 16:13:26.870256382 +0100 IST: dict{
+                inMulticastPkts: 0.10000018299125094
+                inOctets: 38.50007045163161
+                inUcastPkts: 0.20000036598250187
+                outMulticastPkts: 0.10000018299125094
+                outOctets: 12.80002342288012
+            }
+        }
+    }
+    JPE654321: dict{
+        Ethernet1: timeseries{
+            start: 2021-10-26 16:13:16.870363498 +0100 IST
+            end: 2021-10-26 16:13:34.615865 +0100 IST
+            2021-10-26 16:13:16.870363498 +0100 IST: dict{
+                outMulticastPkts: 0
+                outOctets: 0
+            }
+            2021-10-26 16:13:26.870256382 +0100 IST: dict{
+                inMulticastPkts: 0.50000037628384
+                inOctets: 67.638619033792
+            }
+        }
+        Ethernet2: timeseries{
+            start: 2021-10-26 16:13:26.870256382 +0100 IST
+            end: 2021-10-26 16:13:34.615865 +0100 IST
+            2021-10-26 16:13:26.870256382 +0100 IST: dict{
+                inMulticastPkts: 0.10000027274982
+                inOctets: 33.478329283748833
+                inUcastPkts: 0.20000036598250187
+                outMulticastPkts: 0.100000432767384
+                outOctets: 12.828728378483
+            }
+        }
+    }
+
+
+

For a dataset wildcard, the result is built with the same structure. The syntax is as follows:

+
`user/*:/some/path`[queryParameter] # This will get data for all `user` datasets
+`*:/some/path`[queryParameter] # This will get data for all `device` datasets
+
+
+
+
+

Complex path elements

+

Most paths in the database are made of string path elements. In AQL, they are natively handled and +are separated with slashes in queries. However, some paths can contain path elements of different +types, some of which don’t exist in AQL. AQL, however, supports some of them using the curly +brackets syntax.

+
+

Numerical value

+

A numerical literal can be used between the curly brackets, and will produce an int path element if +the literal is an integer literal, and a float path element when it has a decimal part (nil or not).

+
>>> `myDataset:/foo/{12}/bar` # int path element
+>>> `myDataset:/foo/{12.}/bar` # float path element
+>>> `myDataset:/foo/{12.0}/bar` # float path element
+>>> `myDataset:/foo/{12.35}/bar` # float path element
+
+
+
+
+

Boolean value

+

A boolean literal can be used between the curly brackets.

+
>>> `myDataset:/foo/{true}/bar` # bool (true) path element
+>>> `myDataset:/foo/{false}/bar` # bool (false) path element
+>>> `myDataset:/foo/true/bar` # string path element
+>>> `myDataset:/foo/{"true"}/bar` # string path element (identical to the previous one)
+
+
+
+
+

String value

+

A string literal can be used between the curly brackets. This is mostly useful for path elements that +contain a slash

+
>>> `myDataset:/foo/{"my string value"}/bar` # string path element
+>>> `myDataset:/foo/{"my/string/with/slashes"}/bar` # string path element containing slashes
+>>> `myDataset:/foo/my\/string\/with\/slashes/bar` # identical to the previous one
+
+
+
+
+

Map value

+

A map can be input using the JSON syntax (curly brackets and comma-separated colon-linked pairs). +JSON does not know the difference between floats and ints, so a numerical value with a nil decimal +part will be interpreted as an int, and one with a non-nil decimal part will be interpreted as a +float. Can contain nested lists and maps.

+
>>> `myDataset:/foo/{"key": 1.0}/bar` # map("key": int(1)) path element
+>>> `myDataset:/foo/{"key": 1}/bar` # map("key": int(1)) path element
+>>> `myDataset:/foo/{"key": 1.1}/bar` # map("key": float(1.1)) path element
+>>> `myDataset:/foo/{"key": "val", "keyb": true}/bar` # map("key": str("val"), "keyb": bool(true))
+
+
+
+
+

List value

+

A list can be input using the JSON syntax (square brackets and comma-separated values). JSON does +not know the difference between floats and ints, so a numerical value with a nil decimal part will +be interpreted as an int, and one with a non-nil decimal part will be interpreted as a float. Can +contain nested lists and maps

+
>>> `myDataset:/foo/[1.0, 1, 1.1]/bar` # list(int(1), int(1), float(1.1)) path element
+>>> `myDataset:/foo/[true, "str", {"subkey": "subval"}, [1]]/bar` # list(bool(true), str("str"), map("subkey": str("subval")), list([int(1)]))
+
+
+
+
+
+

Query parameter

+

The query parameter is specified within the square brackets attached to the query. It determines +the amount (time range or number of updates) of data to fetch.

+

The parameter must be written as a num or duration literal. It cannot use the value of a variable.

+
+

No parameter

+

If the parameter is not specified, it is equivalent to specifying 0 within the brackets. In that case, +the query will only return the state of data at the current time.

+

This timeseries can contain multiple updates if the keys at this path were last update at different times.

+

In the example below, keys were last updated in 3 different updates, so the timeseries contains 3 updates.

+
>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`
+timeseries {
+    start: 2021-10-26 16:13:26 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:26 +0100 IST: dict{
+        key4: 5
+        key5: 6
+    }
+    2021-10-26 16:13:29 +0100 IST: dict{
+        key3: 2
+    }
+    2021-10-26 16:13:34 +0100 IST: dict{
+        key1: 2
+        key2: 1
+    }
+}
+
+
+
+

Note: Merging the result

+

When getting only the current state (not specifying any parameter), it is common +practice to use the merge function, which will turn a timeseries of dicts into a simple dict, +containing the latest value for each possible key. This allows for direct manipulation of data.

+
+
>>> merge(`analytics:/Devices/JPE12345/path/to/some/interface/data`)
+dict{
+    key1: 2
+    key2: 1
+    key3: 2
+    key4: 5
+    key5: 6
+}
+
+
+
+

Warning

+

Do not confuse the query parameter with the bracket operator that accesses a specific update +in an existing timeseries.

+

In the following example, the first bracket expression is the query parameter, and the second is the +index of the value to get in the resulting timeseries.

+
>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[0][0]
+dict{
+    key4: 5
+    key5: 6
+}
+
+
+

If you want to use the “index-access” bracket operator and not specify a query parameter, you must either +explicitly define the query parameter before, or surround the query with parentheses.

+
>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[0]
+timeseries {
+    start: 2021-10-26 16:13:26 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:26 +0100 IST: dict{
+        key4: 5
+        key5: 6
+    }
+    2021-10-26 16:13:29 +0100 IST: dict{
+        key3: 2
+    }
+    2021-10-26 16:13:34 +0100 IST: dict{
+        key1: 2
+        key2: 1
+    }
+}
+>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`["key4"]
+error: input:1:1: bracket selector of query can only get a num or duration, got str
+>>> (`analytics:/Devices/JPE12345/path/to/some/interface/data`)["key4"]
+error: input:1:16: operator [] applied to timeseries needs a value of type num or time
+>>> (`analytics:/Devices/JPE12345/path/to/some/interface/data`)[0]
+dict{
+    key4: 5
+    key5: 6
+}
+>>> (`analytics:/Devices/JPE12345/path/to/some/interface/data`)[0]["key4"]
+5
+>>> merge(`analytics:/Devices/JPE12345/path/to/some/interface/data`)["key4"]
+5
+
+
+
+
+
+

Number of updates

+

If the square brackets contain a num literal, this num defines what number n of updates to get. +The query will fetch the n latest updates at this path, with each update corresponding to an entry +in the resulting timeseries. However, the length of the timeseries can be superior to n, because +the query also gets the “state” of data before the n updates, i.e. the last update for each key at this +path before the n updates.

+

In the example below, the query requests 3 updates. However, the timeseries returned has a length of 5. +This is because the oldest update of the 3 only updates the value of keys key4 and key5, but not key1, +key2, and key3. Therefore the query also returns the latest update before it for each of these keys. +Here, there are two of these “state” updates: one updates both key1 and key2, and the other updates key3.

+
>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[3]
+timeseries {
+    start: 2021-10-26 16:13:16 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:16 +0100 IST: dict{
+        key1: 1
+        key2: 2
+    }
+    2021-10-26 16:13:23 +0100 IST: dict{
+        key3: 1
+    }
+    2021-10-26 16:13:26 +0100 IST: dict{
+        key4: 5
+        key5: 6
+    }
+    2021-10-26 16:13:29 +0100 IST: dict{
+        key1: 2
+        key3: 1
+    }
+    2021-10-26 16:13:34 +0100 IST: dict{
+        key1: 2
+        key2: 1
+    }
+}
+
+
+

If the oldest of the 3 updates had updated all the keys stored at this path, there would not have been +any “state” update and the length of the timeseries would have been 3:

+
>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[3]
+timeseries {
+    start: 2021-10-26 16:13:26 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:26 +0100 IST: dict{
+        key1: 5
+        key2: 4
+        key3: 3
+        key4: 2
+        key5: 1
+    }
+    2021-10-26 16:13:29 +0100 IST: dict{
+        key1: 2
+        key3: 1
+    }
+    2021-10-26 16:13:34 +0100 IST: dict{
+        key1: 2
+        key2: 1
+    }
+}
+
+
+
+
+

Duration

+

If the square brackets contain a duration literal, this specifies the time range of data returned +by the query.

+

The query will return all the updates that happened at this path during the last d duration, along +with the “state” updates, following the same rules as the number of updates.

+

In the example below, the query fetches the latest 8 seconds of data. In this interval, three updates +happened, the oldest of which only updated key4 and key5, so the returned timeseries also contains +two older updates, which are the latest updates for key1, key2 and key3 before now() - 8s.

+
>>> `analytics:/Devices/JPE12345/path/to/some/interface/data`[8s]
+timeseries {
+    start: 2021-10-26 16:13:16 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:16 +0100 IST: dict{
+        key1: 1
+        key2: 2
+    }
+    2021-10-26 16:13:23 +0100 IST: dict{
+        key3: 1
+    }
+    2021-10-26 16:13:26 +0100 IST: dict{
+        key4: 5
+        key5: 6
+    }
+    2021-10-26 16:13:29 +0100 IST: dict{
+        key1: 2
+        key3: 1
+    }
+    2021-10-26 16:13:34 +0100 IST: dict{
+        key1: 2
+        key2: 1
+    }
+}
+
+
+
+
+

Fixed timestamps range

+

It is also possible to use two timestamps separated with a colon (:). This syntax allows querying +data that was written between these timestamps, along with the state data from before the first +timestamp.

+

Like for every query parameter, it is not possible to use a regular variable as one of the timestamps. +They have to be defined directly within the square brackets, or use an input metavariable (defined +outside of the AQL script scope).

+
>>> `analytics:/path/to/data`[time("2022-01-26T16:00:00+00:00"):time("2022-01-26T16:01:00+00:00")]
+timeseries {
+    start: 2022-01-26 16:00:00 +0000 GMT
+    end: 2022-01-26 16:01:00 +0000 GMT
+    2021-10-26 15:59:30 +0100 IST: dict{
+        key1: 1
+    }
+    2021-10-26 16:00:00 +0100 IST: dict{
+        key1: 2
+    }
+    2021-10-26 16:00:30 +0100 IST: dict{
+        key1: 3
+    }
+    2021-10-26 16:01:00 +0100 IST: dict{
+        key1: 4
+    }
+}
+
+
+

Example with input variables:

+
>>> `analytics:/path/to/data`[_startTime:_endTime]
+timeseries {
+    start: 2022-01-26 16:00:00 +0000 GMT
+    end: 2022-01-26 16:01:00 +0000 GMT
+    2021-10-26 15:59:30 +0100 IST: dict{
+        key1: 1
+    }
+    2021-10-26 16:00:00 +0100 IST: dict{
+        key1: 2
+    }
+    2021-10-26 16:00:30 +0100 IST: dict{
+        key1: 3
+    }
+    2021-10-26 16:01:00 +0100 IST: dict{
+        key1: 4
+    }
+}
+
+
+
+
+
+
+

Square bracket operator

+

When applied to a collection (dict or timeseries), the square bracket operator allows access to +a specific value of that collection.

+
+

Timeseries

+

For a timeseries, the type specified within the square brackets can be either a num for access to +a specific numerical index (starts at 0) in the timeseries, or a time, for access to a the value at +a specific time (if there is no exact match, it will return the latest value before the specied time).

+

When accessing a timeseries value using the square bracket operator, the interpreter sets the metavariables +_bracketTime and _bracketIndex to the exact time and index associated with that value.

+

The num index can be negative, in which case it starts from the end of the timeseries (index -1 is the +last update)

+
>>> myTimeseries
+timeseries {
+    start: 2021-10-26 16:13:16 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:16 +0100 IST: "val1"
+    2021-10-26 16:13:23 +0100 IST: "val2"
+    2021-10-26 16:13:26 +0100 IST: "val3"
+    2021-10-26 16:13:29 +0100 IST: "val4"
+    2021-10-26 16:13:34 +0100 IST: "val5"
+}
+>>> myTimeseries[time("2021-10-26T16:13:25+01:00")]
+val2
+>>> _bracketTime
+2021-10-26 16:13:23 +0100 IST
+>>> _bracketIndex
+1
+>>> myTimeseries[-2]
+val4
+>>> _bracketTime
+2021-10-26 16:13:29 +0100 IST
+>>> _bracketIndex
+3
+
+
+
+
+

Dict

+

The square bracket operator allows access to the value associated with a specific key in a dict. +The key can be of any valid key type:

+
    +
  • num

  • +
  • bool

  • +
  • str

  • +
  • any value returned by the complexKey function (even if type is unknown)

  • +
+
>>> let d = newDict() | setFields("key", 1, 2, 3, complexKey("{\"k\": \"v\"}"), 4)
+>>> d
+dict{
+    2: 3
+    key: 1
+    {"k":"v"}: 4
+}
+>>> d[2]
+3
+>>> d["key"]
+1
+>>> d[complexKey("{\"k\": \"v\"}")]
+4
+
+
+

This operator also allows for setting values in the dict.

+
>>> let d = newDict()
+>>> d["key"] = "value"
+>>> d
+dict{key: value}
+
+
+
+
+
+

If/Else

+

AQL also supports if / else conditions. The syntax is as follows:

+
if condition {
+    # statements
+} else {
+    # statements
+}
+
+
+

It is possible to write only the if block and not the else.

+
if condition {
+    # statements
+}
+
+
+

Variables in AQL are not scoped. This means that variables defined within the scope of the if / else +can be accessed from outside.

+
>>> a
+error: input:1:1: undeclared variable: a
+>>> if 5 > 3 {
+...     let a = 1
+... }
+>>> a
+1
+
+
+

The metavariable _ is set even by statements within the scope of an if / else.

+
>>> let a = 6 * 7
+>>> if a == 42 || a == 6 * 9 {
+...     "a is the answer"
+... } else {
+...     "a is not the answer"
+... }
+>>> _
+a is the answer
+
+
+

However, the if / else statement itself does not have a return value, like a variable assignment. +Therefore, this script will not return "a is not the answer" but will have no return value:

+
let a = 5
+if a == 42 || a == 6 * 9 {
+    "a is the answer"
+} else {
+    "a is not the answer"
+}
+
+
+

To return this value at the end of the script, it is possible to just add a statement that simply +returns the _ value. In that case, the script will return "a is not the answer":

+
let a = 5
+if a == 42 || a == 6 * 9 {
+    "a is the answer"
+} else {
+    "a is not the answer"
+}
+_
+
+
+
+
+

Ternary expressions

+

Ternary expressions allow to use conditions directly within an expression. The syntax is similar to +that of the C language.

+
condition ? valueIfConditionIsTrue : valueIfConditionIsFalse
+
+
+

This can be used in any context that manipulates a value.

+
>>> let a = 2
+>>> let b = a < 3 ? "a lower than 3" : "a greater than 3"
+>>> b
+a lower than 3
+
+
+

Ternary expressions can be nested.

+
>>> let a = 2
+>>> let b = a > 0 ? a > 5 ? "big" : "small" : "negative"
+>>> b
+small
+
+
+

Ternary expressions are mostly used within programmatic filters such as map (see section Filters), +because these filters only allow pure expressions, and statements such as if / else or variable +declarations statements cannot be used in their scope.

+
>>> let data = `analytics:/some/data/path`[16s] | field("avg")
+>>> let threshold = 10
+>>> data | map(_value <= threshold ? _value : "forbidden")
+timeseries {
+    start: 2021-10-26 16:13:16 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:16 +0100 IST: 5
+    2021-10-26 16:13:23 +0100 IST: forbidden
+    2021-10-26 16:13:26 +0100 IST: 3
+    2021-10-26 16:13:29 +0100 IST: 10
+    2021-10-26 16:13:34 +0100 IST: forbidden
+}
+
+
+
+
+

Loops

+

AQL supports two kinds of loops: for and while.

+
+

For loop

+

The for loop iterates over an existing collection (dict or timeseries). The syntax is as follows:

+
for k, v in collection {
+    # statements
+}
+
+
+

In the example above, k takes the current key (or timestamp if the collection is a timeseries), +and v its associated value at each iteration. k and v are not predefined names and can be named +any valid variable name by the user:

+
>>> let myDict = newDict() | setFields("k1", 1, "k2", 2)
+>>> let s = ""
+>>> for myKey, myValue in myDict {
+...     let s = s + "{" + str(myKey) + ": " + str(myValue) + "}"
+... }
+>>> s
+{k1: 1}{k2: 2}
+
+
+

It is possible to specify only one variable instead of both key and value. In this case, the variable +will only take the value at each iteration, and the timestamp/key will not be used.

+
>>> let myDict = newDict() | setFields("k1", 1, "k2", 2)
+>>> let i = 0
+>>> for val in myDict {
+...     let i = i + val
+... }
+>>> i
+3
+
+
+
+
+

While loop

+

While loops will iterate as long as the specified condition is true. The syntax is as follows:

+
while condition {
+    # statements
+}
+
+
+

Here is an example that computes the factorial of 6.

+
>>> let fact6 = 1
+>>> let a = 1
+>>> while a <= 6 {
+...     let fact6 = fact6 * a
+...     let a = a + 1
+... }
+>>> fact6
+720
+
+
+
+
+
+

Functions

+

The Standard Library of AQL offers a wide range of functions. Each of them is documented in the +Standard Library page.

+

However, it is not possible to define new functions in AQL.

+

The syntax to call a standard library function is as follows:

+
functionName(argument1)
+functionName(argument1, argument2)
+
+
+

The number of arguments varies depending on the function.

+

Some examples:

+
>>> let data = `analytics:/some/data/path`[10s]
+>>> length(data) # length returns the length of a dict or timeseries
+5
+>>> let avgData = data | field("avg")
+>>> avgData
+timeseries {
+    start: 2021-10-26 16:13:25 +0100
+    end: 2021-10-26 16:13:34 +0100
+    2021-10-26 16:13:25 +0100 IST: 5
+    2021-10-26 16:13:27 +0100 IST: 2
+    2021-10-26 16:13:29 +0100 IST: 3
+    2021-10-26 16:13:31 +0100 IST: 10
+    2021-10-26 16:13:33 +0100 IST: 1
+}
+>>> mean(avgData)
+4.2
+
+
+
+
+

Filters

+

Filters are one of the most powerful features in AQL. They allow filtering, formatting, and refining of data +returned by queries very easily and much more efficiently than with loops and manual data manipulation.

+

The AQL Standard Library offers a wide range of filters, designed to adapt to the data structures of +the CloudVision database. Each of them is documented in the Standard Library page.

+

Filters can only be applied to a collection (dict or timeseries), and do not alter the data in the +filtered collection. They return a new collection of the same type, with its content filtered or altered.

+

The syntax is as follows:

+
collection | filterName(argument1, argument2)
+
+
+

The number of arguments varies depending on the filter, and filters can be chained. Some filters, like +fields or setFields for example, take a variable number of arguments.

+
collection | filter1(argument1) | filter2(argument1, argument2) | filter3(argument1)
+
+
+

Some filters, such as map or where take expressions as arguments, and set metavariables that can +be used in these expressions to manipulate the content of the collection. Here are some examples +with a dict but these filters work with timeseries as well. For more examples with either type of +collection, see the detailed documentation for each filter.

+
>>> let d = newDict() | setFields("k1", 1, "k2", 2, "k3", 3)
+>>> d
+dict{
+    k1: 1
+    k2: 2
+    k3: 3
+}
+>>> d | map(_value * 10)
+dict{
+    k1: 10
+    k2: 20
+    k3: 30
+}
+>>> d
+dict{
+    k1: 1
+    k2: 2
+    k3: 3
+}
+>>> d | map(_value * 10) | where(_value <= 20)
+dict{
+    k1: 10
+    k2: 20
+}
+>>> d | map(str(_value * 10) + _key)
+dict{
+    k1: 10k1
+    k2: 20k2
+    k3: 30k3
+}
+
+
+

The expression within a filter can use nested filters.

+
>>> let d = newDict() | setFields("d1", newDict() | setFields("k1", 1), "d2", newDict() | setFields("k1", 2))
+dict{
+        d1: dict{
+                k1: 1
+        }
+        d2: dict{
+                k1: 2
+        }
+}
+>>> d | map(_value * 10)
+error: input:1:4: input:1:15: operator * cannot be used with dict and num
+>>> d | map(_value | map(_value * 10))
+dict{
+        d1: dict{
+                k1: 10
+        }
+        d2: dict{
+                k1: 20
+        }
+}
+
+
+
+
+

Directives

+

Directives allow setting some options before running an AQL script. They must be specified at the beginning +of the script code and will be set for the entire execution.

+

The syntax is as follows:

+
%directiveName = true|false
+
+
+

The only directive currently allowed is includeDecommissionedDevices. It makes device dataset +wildcards include decommissioned devices’ datasets. By default, these datasets are not included.

+
%includeDecommissionedDevices = true
+
+`*:/Sysdb/some/path/to/data`
+
+
+
+
+

Named wildcards and per-value execution

+
+

Warning

+

This section covers features that apply outside of the scope of a single AQL script execution. +Named wildcards are a part of AQL syntax but will modify how the interpreter behaves, by making +it run the script multiple times instead of one, with different input variables at each execution.

+

This execution can then produce multiple outputs.

+
+
+

Default behaviour

+

It is possible to insert a named wildcard into a query with the following syntax: <wildcardName>. +When a named wildcard is set in a query, the interpreter catches it before running the AQL script; +instead of running it once, it runs the script multiple times for each path element matching the named +wildcard.

+

In each run, the value of the path element is also set by the interpreter as a metavariable. +The name of that variable is always prefixed with an underscore, like all metavariables, but you do not +have to specify that underscore in the wildcard name. Therefore, these two syntaxes have the exact same +result, with the path element being stored in variable _device

+
`analytics:/Devices/<device>/interfaces/data`
+
+
+
`analytics:/Devices/<_device>/interfaces/data`
+
+
+

Example:

+

Regular wildcard:

+
let data = `*:/some/path/to/data`[1m]
+data # This contains the data for all datasets (type = dict of timeseries)
+
+
+

Named wildcard:

+
let data = `<d>:/some/path/to/data`[1m]
+let deviceName = _d # deviceName is a single string value, the name of the dataset in the current run
+data # This contains only the data for one dataset (type = timeseries)
+
+
+

For the named wildcard, the AQL script runs multiple times and the interpreter returns the list of all the +outputs. The user only has to manage data for one single dataset within the AQL code.

+

For the regular wildcard, the AQL script runs only once, and contains the data of a datasets in a dict. +The user has to deal with the data of all the datasets manually.

+
+
+

Manual user input

+

When running AQL scripts through CLI, the Service API, or directly using the AQL interpreter library, +it is possible to pass input variables to manually control the multiple runs of named wildcards from +outside the scope of the AQL script.

+

In the AQL library, this is handled through the inputVars parameter, in the Service API, through the +varsets field.

+

In any case, the field is a list that defines the list of times the query will be run. +Each element of the list is a list or map of the variables that the interpreter will set in the environment +before running the AQL script.

+

If these varsets contain values that match the variable name of a named wildcard, the interpreter will +not perform the global GET on this named wildcard and instead run the query one or several times, following +the runs defined in the varset.

+

Example:

+

With these input variable sets:

+
[
+    {"_d": "JPE123456", "_i": "Ethernet5"},
+    {"_d": "JPE123456", "_i": "Ethernet6"},
+    {"_d": "JPE654321", "_i": "Ethernet1"}
+]
+
+
+

The following query will just run three times, twice for device JPE123456 (with interface Ethernet5 +the first time and Ethernet6 the second), and once for device JPE654321, with interface Ethernet1.

+
let interfaceData = `<d>:/Sysdb/hardware/archer/xcvr/status/all/<i>`[10m]
+let deviceAndInterfaceNames = _d + " " + _i # This is just a string containing the device and interface name
+interfaceData # This is a timeseries containing the last 10 mins of data for the current intf and device
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/index_examples.html b/index_examples.html new file mode 100644 index 0000000..3035108 --- /dev/null +++ b/index_examples.html @@ -0,0 +1,194 @@ + + + + + + + AQL Examples — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

AQL Examples

+ +
+

Note

+

Some telemetry states are not streamed by default and they would need to be added to the TerminAttr include list using the tastreaming.TerminattrStreaming service API, e.g.:

+

On-prem example:

+
curl -sS -kX POST --header 'Accept: application/json' -b access_token=`cat token` \
+   'https://192.0.2.1/api/v3/services/tastreaming.TerminattrStreaming/SubscribeTAPaths' \
+
+   -d '{ "filter": { "app_name": "app1", "include_paths": [ "/Sysdb/bridging/igmpsnooping" ]}}'
+
+
+

CVaaS example:

+
curl -sS -kX POST --header 'Accept: application/json' -b access_token=`cat token` \
+   'https://www.arista.io/api/v3/services/tastreaming.TerminattrStreaming/SubscribeTAPaths' \
+
+   -d '{ "filter": { "app_name": "app1", "include_paths": [ "/Sysdb/bridging/igmpsnooping" ]}}'
+
+
+

Result:

+
[{}]
+
+
+

For CVaaS please make sure to use the correct regional URL:

+
    +
  • United States 1a: www.cv-prod-us-central1-a.arista.io

  • +
  • United States 1c: www.cv-prod-us-central1-c.arista.io

  • +
  • Japan: www.cv-prod-apnortheast-1.arista.io

  • +
  • Germany: www.cv-prod-euwest-2.arista.io

  • +
  • Australia: www.cv-prod-ausoutheast-1.arista.io

  • +
  • Canada: www.cv-prod-na-northeast1-b.arista.io

  • +
  • United Kingdom: www.cv-prod-uk-1.arista.io

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/index_stdlib.html b/index_stdlib.html new file mode 100644 index 0000000..e107bdb --- /dev/null +++ b/index_stdlib.html @@ -0,0 +1,2763 @@ + + + + + + + AQL Standard Library — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

AQL Standard Library

+ +
+

Functions

+
+

General functions

+
+

now

+

Added in revision 1

+

Function now returns the current time. Time is constant across all the script, so that all operations and queries have the same reference time.

+
>>> now()
+2019-09-01T14:05:10Z
+
+
+
+
+

length

+

Added in revision 1

+

Function length returns the number of elements in a str, a timeseries or a dict.

+ +
>>> length(`analytics:/path/to/data`[0])
+1
+
+
+
+
+

merge

+

Added in revision 1

+

Function merge returns a union of all the dicts contained in a timeseries. In case of key collision, the latest entry is kept.

+ +
>>> `analytics:/path/to/data`[1]
+timeseries{
+        tstamp1: dict{key1: val1, key2: val2, key3: val3}
+        tstamp2: dict{key1: val4, key2: val5}
+}
+>>> merge(_)
+dict{key1: val4, key2: val5, key: val3}
+
+
+
+
+

deletes

+

Added in revision 1

+

Function deletes returns a timeseries of dicts used as sets of the delete keys from the input timeseries. +Only works with unfiltered timeseries, as most filters remove the deletes entries. An empty dict as value means the update is a DeleteAll

+
    +
  • The first and only parameter is the timeseries

  • +
+
>>> deletes(`analytics:/path/to/data`[200])
+timeseries{
+        tstamp1: dict{key1: nil, key2: nil}
+        tstamp2: dict{} # this deletes all keys
+}
+
+
+
+
+

equal

+

Added in revision 1

+

Function equal performs a cross-type equality check on the given arguments using type coercions.

+
    +
  • The first argument is a value of any type

  • +
  • The second argument is a value of any type

  • +
+
>>> equal("1", 1)
+true
+>>> equal(1, true)
+true
+>>> equal(0, true)
+false
+
+
+
+
+

complexKey

+

Added in revision 1

+

Function complexKey parses a string containing a literal or json object. +Numerical values without floating-point will produce an integer. +Numerical values with a floating point will produce a float64 (num). +Boolean literals will produce a boolean (bool). +Values surrounded with {} or [] will be parsed as JSON. +For all cases except float64 (num) and bool, the returned value will be of type unknown (internal interpreter type), but can be used to access complex keys in dicts.

+
    +
  • The first and only parameter is the str to parse

  • +
+
>>> complexKey("1")
+int(1) # AQL type is unknown
+>>> complexKey("1.2")
+float64(1.2) # AQL type is num
+>>> complexKey("{\"key1\": 1, \"key2\": true}")
+{"key1":1,"key2":true}# AQL type is unknown
+>>> complexKey("[1, 2, true]")
+[1,2,true] # AQL type is unknown
+
+
+
+
+
+

Dicts functions

+
+

newDict

+

Added in revision 1

+

Function newDict returns a new empty dict.

+
>>> newDict()
+dict{}
+
+
+
+
+

dictRemove

+

Added in revision 1

+

Function dictRemove removes a given key from a dict.

+
    +
  • The first argument is the dict

  • +
  • The second argument is the key to remove

  • +
+
>>> let d = newDict()
+>>> d["key"] = 1
+>>> d["key2"] = 2
+>>> d
+dict{"key": 1, "key2": 2}
+>>> dictRemove(d, "key")
+>>> d
+dict{"key2": 2}
+
+
+
+
+

dictHasKey

+

Added in revision 1

+

Function dictHasKey returns true if a dict contains the specified key, false if it doesn’t.

+
    +
  • The first parameter is the dict

  • +
  • The second parameter is the key

  • +
+
>>> let d = newDict()
+>>> d["key"] = 1
+>>> d["key2"] = 2
+>>> d
+dict{"key": 1, "key2": 2}
+>>> dictHasKey(d, "key")
+true
+>>> dictHasKey(d, "key3")
+false
+
+
+
+
+

dictKeys

+

Added in revision 1

+

Function dictKeys returns a timeseries with the list of keys in a dict.

+
    +
  • The first and only parameter is the dict

  • +
+
>>> let d = newDict()
+>>> d["key"] = 1
+>>> d["key2"] = 2
+>>> d
+dict{"key": 1, "key2": 2}
+>>> dictKeys(d)
+timeseries{
+        0000-00-00 00:00:00.000000001: "key"
+        0000-00-00 00:00:00.000000002: "key2"
+}
+
+
+
+
+

unnestTimeseries

+

Added in revision 4

+

Function unnestTimeseries merges multiple timeseries nested in dicts, pushes them to the top level, +and returns a single flattened timeseries where the former top-level dicts are now nested within +the timeseries’ values. The input data is typically what a query using * wildcards would return.

+
    +
  • The first and only parameter is a dict that contains timeseries, either directly in the value, or nested in more levels of dict.

  • +
+
+Example
>>> let ts = `analytics:/Devices/*/versioned-data/interfaces/data/*/aggregate/hardware/xcvr/1m`[1m] | recmap(2, _value | field("temperature") | field("avg"))
+>>> let d = newDict() | setFields("AA", ts)
+>>> d
+dict{AA: dict{
+                HSH14280171: dict{
+                        Ethernet50: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 34.184087816704434
+                                2022-08-16 17:11:00 +0200 CEST: 34.18519049983288
+                        }
+                        Ethernet51: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 34.84759166533158
+                                2022-08-16 17:11:00 +0200 CEST: 34.84247911778802
+                        }
+                }
+                JAS17070003: dict{
+                        Ethernet50: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 30.65325390983907
+                                2022-08-16 17:11:00 +0200 CEST: 30.65664047328105
+                        }
+                        Ethernet51: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 27.58473123455124
+                                2022-08-16 17:11:00 +0200 CEST: 27.597529745280074
+                        }
+                }
+        }}
+>>> unnestTimeseries(d)
+timeseries{
+        start: 2022-08-16 17:10:21.217673 +0200 CEST
+        end: 2022-08-16 17:11:21.217673 +0200 CEST
+        2022-08-16 17:10:00 +0200 CEST: dict{AA: dict{
+                        HSH14280171: dict{
+                                Ethernet50: 34.184087816704434
+                                Ethernet51: 34.84759166533158
+                        }
+                        JAS17070003: dict{
+                                Ethernet50: 30.65325390983907
+                                Ethernet51: 27.58473123455124
+                        }
+                }}
+        2022-08-16 17:11:00 +0200 CEST: dict{AA: dict{
+                        HSH14280171: dict{
+                                Ethernet50: 34.18519049983288
+                                Ethernet51: 34.84247911778802
+                        }
+                        JAS17070003: dict{
+                                Ethernet50: 30.65664047328105
+                                Ethernet51: 27.597529745280074
+                        }
+                }}
+}
+>>> let b = `analytics:/Devices/JPE17191574/versioned-data/interfaces/data/Ethernet50/aggregate/hardware/xcvr/1m`[2m] | field("voltage") | field("max")
+>>> let dd = newDict() | setFields("BB", b)
+>>> let ddd = newDict() | setFields("DD", ts, "EE", dd)
+>>> d["FF"]=ddd
+>>> d
+dict{
+        AA: dict{
+                HSH14280171: dict{
+                        Ethernet50: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 34.184087816704434
+                                2022-08-16 17:11:00 +0200 CEST: 34.18519049983288
+                        }
+                        Ethernet51: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 34.84759166533158
+                                2022-08-16 17:11:00 +0200 CEST: 34.84247911778802
+                        }
+                }
+                JAS17070003: dict{
+                        Ethernet50: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 30.65325390983907
+                                2022-08-16 17:11:00 +0200 CEST: 30.65664047328105
+                        }
+                        Ethernet51: timeseries{
+                                start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                2022-08-16 17:10:00 +0200 CEST: 27.58473123455124
+                                2022-08-16 17:11:00 +0200 CEST: 27.597529745280074
+                        }
+                }
+        }
+        FF: dict{
+                DD: dict{
+                        HSH14280171: dict{
+                                Ethernet50: timeseries{
+                                        start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                        end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                        2022-08-16 17:10:00 +0200 CEST: 34.184087816704434
+                                        2022-08-16 17:11:00 +0200 CEST: 34.18519049983288
+                                }
+                                Ethernet51: timeseries{
+                                        start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                        end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                        2022-08-16 17:10:00 +0200 CEST: 34.84759166533158
+                                        2022-08-16 17:11:00 +0200 CEST: 34.84247911778802
+                                }
+                        }
+                        JAS17070003: dict{
+                                Ethernet50: timeseries{
+                                        start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                        end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                        2022-08-16 17:10:00 +0200 CEST: 30.65325390983907
+                                        2022-08-16 17:11:00 +0200 CEST: 30.65664047328105
+                                }
+                                Ethernet51: timeseries{
+                                        start: 2022-08-16 17:10:21.217673 +0200 CEST
+                                        end: 2022-08-16 17:11:21.217673 +0200 CEST
+                                        2022-08-16 17:10:00 +0200 CEST: 27.58473123455124
+                                        2022-08-16 17:11:00 +0200 CEST: 27.597529745280074
+                                }
+                        }
+                }
+                EE: dict{BB: timeseries{
+                                start: 2022-08-16 17:14:06.794969 +0200 CEST
+                                end: 2022-08-16 17:16:06.794969 +0200 CEST
+                                2022-08-16 17:14:00 +0200 CEST: 3.2909
+                                2022-08-16 17:15:00 +0200 CEST: 3.2909
+                                2022-08-16 17:16:00 +0200 CEST: 3.2909
+                        }}
+        }
+}
+>>> unnestTimeseries(d)
+timeseries{
+        start: 2022-08-16 17:10:21.217673 +0200 CEST
+        end: 2022-08-16 17:16:06.794969 +0200 CEST
+        2022-08-16 17:10:00 +0200 CEST: dict{
+                AA: dict{
+                        HSH14280171: dict{
+                                Ethernet50: 34.184087816704434
+                                Ethernet51: 34.84759166533158
+                        }
+                        JAS17070003: dict{
+                                Ethernet50: 30.65325390983907
+                                Ethernet51: 27.58473123455124
+                        }
+                }
+                FF: dict{DD: dict{
+                                HSH14280171: dict{
+                                        Ethernet50: 34.184087816704434
+                                        Ethernet51: 34.84759166533158
+                                }
+                                JAS17070003: dict{
+                                        Ethernet50: 30.65325390983907
+                                        Ethernet51: 27.58473123455124
+                                }
+                        }}
+        }
+        2022-08-16 17:11:00 +0200 CEST: dict{
+                AA: dict{
+                        HSH14280171: dict{
+                                Ethernet50: 34.18519049983288
+                                Ethernet51: 34.84247911778802
+                        }
+                        JAS17070003: dict{
+                                Ethernet50: 30.65664047328105
+                                Ethernet51: 27.597529745280074
+                        }
+                }
+                FF: dict{DD: dict{
+                                HSH14280171: dict{
+                                        Ethernet50: 34.18519049983288
+                                        Ethernet51: 34.84247911778802
+                                }
+                                JAS17070003: dict{
+                                        Ethernet50: 30.65664047328105
+                                        Ethernet51: 27.597529745280074
+                                }
+                        }}
+        }
+        2022-08-16 17:14:00 +0200 CEST: dict{FF: dict{EE: dict{BB: 3.2909}}}
+        2022-08-16 17:15:00 +0200 CEST: dict{FF: dict{EE: dict{BB: 3.2909}}}
+        2022-08-16 17:16:00 +0200 CEST: dict{FF: dict{EE: dict{BB: 3.2909}}}
+}
+
+
+
+
+
+

Data Analysis Functions

+
+

groupby

+

Added in revision 1

+

Function groupby, applied to a timeseries, returns a dict with keys corresponding to the ‘group by field’ parameter, +and values corresponding to the associate method and field.

+

The function takes 4 parameters:

+
    +
  • A timeseries of dicts to apply this function to

  • +
  • The name of the ‘group by field’ (a str)

  • +
  • The name of one of the supported associative methods (a str)

  • +
  • The name of the field whose values will be operated on by the associative method (a str)

  • +
+

The entries in the timeseries are grouped by the values of the field from parameter 1. +For each entry, the value corresponding to the associative field of parameter 4 is obtained. +This results in a map with entries of the following format:

+
    +
  • key: values of the ‘group by field’

  • +
  • value: lists of values of the ‘associative field’

  • +
+

On each of these lists, the following associative methods can be applied:

+
    +
  • count: returns the length of the list (i.e. the item count)

  • +
  • max: returns the max entry in the list

  • +
  • mean: returns the mean of the values in the list

  • +
  • min: returns the min entry in the list

  • +
  • sum: returns the sum of the entries in the list

  • +
+
>>> `analytics:/path/to/data`[3]
+timeseries{
+        tstamp1: dict{"name": "name1", "value": 1}
+        tstamp2: dict{"name": "name2", "value": 10}
+        tstamp3: dict{"name": "name1", "value": 2}
+        tstamp4: dict{"name": "name2", "value": 11}
+}
+>>> let ts = _
+>>> groupby(ts, "name", "mean", "value")
+dict{
+        "name1": 1.5
+        "name2": 10.5
+}
+>>> groupby(ts, "name", "count", "value")
+dict{
+        "name1": 2
+        "name2": 2
+}
+>>> groupby(ts, "name", "sum", "value")
+dict{
+        "name1": 3
+        "name2": 21
+}
+
+
+
+
+

histogram

+

Added in revision 1

+

Function histogram, for a given timeseries of non-dict values, returns a dict with entries of the following format:

+
    +
  • key: value in the timeseries (range if a timeseries of num values)

  • +
  • value: time-weighted frequency in the timeseries

  • +
+

Arguments:

+
    +
  • A timeseries of non-dict values is the only argument to this function

  • +
+
>>> `analytics:/path/to/data`[3] | field("strfield")
+timeseries{
+        start: 2019-08-31 00:00:00
+        end: 2019-08-31 00:12:00
+        2019-08-31 00:00:00: "string1"
+        2019-08-31 00:01:00: "string2"
+        2019-08-31 00:10:00: "string1"
+        2019-08-31 00:11:00: "string1"
+}
+>>> histogram(_)
+dict{
+        "string1": 0.25
+        "string2": 0.75
+} # the count is weighted accordingly to the intervals
+>>> `analytics:/path/to/data`[5] | field("numfield")
+timeseries{
+        start: 2019-08-31 00:00:00
+        end: 2019-08-31 01:00:00
+        2019-08-31 00:00:00: 1
+        2019-08-31 00:01:00: 1.01
+        2019-08-31 00:10:00: 1.011
+        2019-08-31 00:30:00: 5.2
+        2019-08-31 00:44:00: 5.22
+        2019-08-31 00:56:00: 5.23
+}
+>>> histogram(_)
+dict{
+        "1.0-1.011": 0.5
+        "5.2-5.23": 0.5
+} # the count is weighted accordingly to the intervals
+
+
+
+
+

dhistogram

+

Added in revision 1

+

Function dhistogram, has a similar behaviour as histogram but its result is not time-weighted. +For a given timeseries of non-dict values, returns a dict with entries of the following format:

+ +

Arguments:

+

A timeseries of non-dict values is the only argument to this function

+
>>> `analytics:/path/to/data`[3] | field("strfield")
+timeseries{
+        start: 2019-08-31 00:00:00
+        end: 2019-08-31 00:05:00
+        2019-08-31 00:00:00: "string1"
+        2019-08-31 00:01:00: "string2"
+        2019-08-31 00:10:00: "string1"
+        2019-08-31 00:11:00: "string1"
+} # the count does not depend of the time intervals between the updates
+>>> dhistogram(_)
+dict{
+        "string1": 3
+        "string2": 1
+} # the count does not depend of the time intervals between the updates
+>>> `analytics:/path/to/data`[5] | field("numfield")
+timeseries{
+        2019-08-31 00:00:00: 1
+        2019-08-31 00:01:00: 1.01
+        2019-08-31 00:10:00: 1.011
+        2019-08-31 00:30:00: 5.2
+        2019-08-31 00:44:12: 5.22
+        2019-08-31 02:01:34: 5.23
+}
+>>> dhistogram(_)
+dict{
+        "1.0-1.011": 3
+        "5.2-5.23": 3
+} # the count does not depend of the time intervals between the updates
+
+
+
+
+

aggregate

+

Added in revision 4

+

Function aggregate merges multiple timeseries contained in a dict (like the result of a +wildcarded query) using the associative method specified in the second parameter. The dict must +contain timeseries, all of which must contain identical timestamps.

+

If one of the timeseries is empty, it will be ignored.

+

If some values’ timestamps are not matched in all the other non-empty timeseries of the dict, +these timestamp-value pairs will not be present in the output timeseries.

+

aggregate returns a simple timeseries with the aggregated data of all the input timeseries.

+
    +
  • The first argument is the dict containing timeseries to aggregate

  • +
  • The second argument is the name of the associative method to apply

  • +
+

Like with groupby, the following associative methods can be applied:

+
    +
  • count: returns the length of the list (i.e. the item count)

  • +
  • max: returns the max entry in the list (requires the timeseries to be numerical)

  • +
  • mean: returns the mean of the values in the list (requires the timeseries to be numerical)

  • +
  • min: returns the min entry in the list (requires the timeseries to be numerical)

  • +
  • sum: returns the sum of the entries in the list (requires the timeseries to be numerical)

  • +
+
>>> let data = `analytics:/Devices/*/versioned-data/interfaces/data/*/aggregate/hardware/xcvr/15m`[1h]
+>>> let avg = data | recmap(2, _value | field("temperature") | field("avg"))
+>>> avg
+        JPE123456: dict{
+                Ethernet1: timeseries{
+                        start: 2021-11-09 13:02:18.923904 +0000 GMT
+                        end: 2021-11-09 13:32:18.923904 +0000 GMT
+                        2021-11-09 13:00:00 +0000 GMT: 28.64315689104305
+                        2021-11-09 13:15:00 +0000 GMT: 28.64771549594622
+                        2021-11-09 13:30:00 +0000 GMT: 28.647003241959368
+                }
+                Ethernet2: timeseries{
+                        start: 2021-11-09 13:02:18.923904 +0000 GMT
+                        end: 2021-11-09 13:32:18.923904 +0000 GMT
+                        2021-11-09 13:00:00 +0000 GMT: 26.52073192182222
+                        2021-11-09 13:15:00 +0000 GMT: 26.57132998707
+                        2021-11-09 13:30:00 +0000 GMT: 26.562415784963335
+                }
+                [...]
+        }
+        JPE654321: dict{
+                Ethernet1: timeseries{
+                        start: 2021-11-09 13:02:18.923904 +0000 GMT
+                        end: 2021-11-09 13:32:18.923904 +0000 GMT
+                        2021-11-09 13:00:00 +0000 GMT: 27.872056741171672
+                        2021-11-09 13:15:00 +0000 GMT: 26.422506200403397
+                        2021-11-09 13:30:00 +0000 GMT: 27.889330661612725
+                }
+                Ethernet2: timeseries{
+                        start: 2021-11-09 13:02:18.923904 +0000 GMT
+                        end: 2021-11-09 13:32:18.923904 +0000 GMT
+                        2021-11-09 13:00:00 +0000 GMT: 25.501376131906685
+                        2021-11-09 13:15:00 +0000 GMT: 24.06172043150084
+                        2021-11-09 13:30:00 +0000 GMT: 25.520567819910998
+                }
+                [...]
+        }
+        [...]
+>>> let deviceAvg = avg | map(aggregate(_value, "mean"))
+>>> deviceAvg
+        JPE123456: timeseries{
+                start: 2021-11-09 13:02:18.923904 +0000 GMT
+                end: 2021-11-09 13:32:18.923904 +0000 GMT
+                2021-11-09 13:00:00 +0000 GMT: 29.46781924765547
+                2021-11-09 13:15:00 +0000 GMT: 28.739134103556832
+                2021-11-09 13:30:00 +0000 GMT: 29.756429823529587
+        }
+        JPE654321: timeseries{
+                start: 2021-11-09 13:02:18.923904 +0000 GMT
+                end: 2021-11-09 13:32:18.923904 +0000 GMT
+                2021-11-09 13:00:00 +0000 GMT: 27.581944406432633
+                2021-11-09 13:15:00 +0000 GMT: 27.60952274150811
+                2021-11-09 13:30:00 +0000 GMT: 27.60470951346135
+        }
+        [...]
+>>> aggregate(deviceAvg, "mean") # average temp accross all interfaces of all devices
+timeseries{
+        start: 2021-11-09 13:02:18.923904 +0000 GMT
+        end: 2021-11-09 13:32:18.923904 +0000 GMT
+        2021-11-09 13:00:00 +0000 GMT: 33.261383897237806
+        2021-11-09 13:15:00 +0000 GMT: 33.16722874112898
+        2021-11-09 13:30:00 +0000 GMT: 33.37487749955894
+}
+
+
+
+
+
+

Math functions

+
+

abs

+

Added in revision 1

+

Function abs returns the absolute value (num) of the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the absolute value \(\lvert x \lvert\) is wanted

  • +
+
>>> abs(-11)
+11
+>>> abs(200)
+200
+
+
+
+
+

ceil

+

Added in revision 1

+

Function ceil returns the closest integer (num) succeeding the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the ceil \(\lceil x \rceil\) is wanted

  • +
+
>>> ceil(12)
+12
+>>> ceil(12.1)
+13
+>>> ceil(-12.1)
+-12
+
+
+
+
+

floor

+

Added in revision 1

+

Function floor returns the closest integer (num) preceding the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the floor \(\lfloor x \rfloor\) is wanted

  • +
+
>>> floor(3)
+3
+>>> floor(3.2)
+3
+>>> floor(-3.2)
+-4
+
+
+
+
+

trunc

+

Added in revision 1

+

Function trunc returns the truncated (num) given value.

+
    +
  • The first and only argument is the value (num) to be truncated

  • +
+
>>> trunc(2.6)
+2
+>>> trunc(-2.49)
+-2
+
+
+
+
+

exp

+

Added in revision 1

+

Function exp returns the exponential (num) of the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the exp \(e^x\) is wanted

  • +
+
>>> exp(0)
+1
+>>> exp(12.1)
+179871.86225375105
+
+
+
+
+

factorial

+

Added in revision 1

+

Function factorial returns the factorial (num) of the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the factorial \(x!\) is wanted

  • +
+
>>> factorial(3)
+6
+
+
+
+
+

gcd

+

Added in revision 1

+

Function gcd returns the greatest common divisor (num) of two given integers.

+
    +
  • The first two arguments are the integers (num) of which the GCD is wanted

  • +
+
>>> gcd(25, 30)
+5
+
+
+
+
+

log

+

Added in revision 1

+

Function log returns the natural log (num) of the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the natural log \(log_e x\) is wanted

  • +
+
>>> log(10)
+2.302585092994046
+
+
+
+
+

log10

+

Added in revision 1

+

Function log10 returns the decimal log (num) of the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the decimal log \(log_{10} x\) is wanted

  • +
+
>>> log10(10)
+1
+
+
+
+
+

pow

+

Added in revision 1

+

Function pow returns the first given value (num) to the power of the second given value.

+
    +
  • The two arguments are the values \(x\) (num), \(y\) (num) used to compute \(x^y\)

  • +
+
>>> pow(3, 2)
+9
+>>> pow(9, 1/2)
+3
+
+
+
+
+

round

+

Added in revision 1

+

Function round returns the rounded (num) given value.

+
    +
  • The first and only argument \(x\) is the value used to compute \(\lfloor x\rceil\) i.e. the rounded value (num)

  • +
+
>>> round(2.5)
+3
+>>> round(2.49)
+2
+
+
+
+
+

sqrt

+

Added in revision 1

+

Function sqrt returns the square root (num) of the given value.

+
    +
  • The first and only argument \(x\) is the value (num) of which the square root \(\sqrt{x}\) is wanted

  • +
+
>>> sqrt(9)
+3
+
+
+
+
+

max

+

Added in revision 1

+

Function max returns the max value (num) in a timeseries or a dict.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> max(_)
+200
+
+
+
+
+

min

+

Added in revision 1

+

Function min returns the min value (num) in a timeseries or a dict.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> min(_)
+1
+
+
+
+
+

formatInt

+

Added in revision 4

+

Function formatInt formats a num into a str using the specified base. The num will be treated +as an integer and any decimal part will be truncated.

+
    +
  • The first argument is the num to convert

  • +
  • The second argument is the base (num)

  • +
+
>>> formatInt(4, 2)
+100
+>>> formatInt(4.5, 2)
+100
+>>> formatInt(33, 2)
+100001
+>>> formatInt(15, 16)
+f
+>>> formatInt(29, 16)
+1d
+>>> type(_)
+str
+
+
+
+
+

formatFloat

+

Added in revision 4

+

Function formatFloat formats a num (float64) into a str, according to the specified format and +precision.

+
    +
  • The first argument is the num to convert

  • +
  • The second argument is a str of one letter describing the format:

    +
    +
      +
    • 'b': binary exponent

    • +
    • 'e': decimal exponent

    • +
    • 'f': no exponent

    • +
    • 'x': hexadecimal fraction and binary exponent

    • +
    +
    +
  • +
  • The third argument is :ref:a num specifying the precision, i.e. the number of digits after the decimal +point

  • +
+
>>> formatFloat(15682.8729, "e", 10)
+1.5682872900e+04
+>>> formatFloat(15682.8729, "f", 10)
+15682.8729000000
+>>> formatFloat(15, "x", 3)
+0x1.e00p+03
+>>> formatFloat(15, "b", 3)
+8444249301319680p-49
+>>> type(_)
+str
+
+
+
+
+
+

Stats functions

+
+

dsum

+

Added in revision 1

+

Function dsum returns the non-weighted sum of values (num) in a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dsum(_)
+216
+
+
+
+
+

dmean

+

Added in revision 1

+

Function dmean returns the non-weighted mean value (num) of a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dmean(_)
+54
+
+
+
+
+

dmedian

+

Added in revision 1

+

Function dmedian returns the non-weighted median (num) of a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dmedian(_)
+2
+
+
+
+
+

dpercentile

+

Added in revision 1

+

Function dpercentile returns the non-weighted nth percentile (num) of a timeseries.

+
    +
  • The first argument is a timeseries containing plain num values

  • +
  • The second argument is a num specifying the percentile. If it is greater than 100 or lower than 0, the return value will be 0.

  • +
+
>>> let a = `analytics:/path/to/data`[3] | field("numfield")
+>>> a
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dpercentile(a, 50)
+2
+>>> dpercentile(a, 90)
+200
+
+
+
+
+

dvariance

+

Added in revision 1

+

Function dvariance returns the non-weighted statistical variance (num) of a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dvariance(_)
+9503.333333333334
+
+
+
+
+

dstddev

+

Added in revision 1

+

Function dstddev returns the non-weighted standard deviation (num) of a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> let a = `analytics:/path/to/data`[3] | field("numfield")
+>>> a
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dstddev(a)
+97.48504158758581
+>>> sqrt(dvariance(a))
+97.48504158758581
+
+
+
+
+

dskew

+

Added in revision 1

+

Function dskew returns the non-weighted skewness of distribution (num) for data in a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dskew(_)
+0.7431002727844832
+
+
+
+
+

dkurtosis

+

Added in revision 1

+

Function dkurtosis returns the non-weighted kurtosis of distribution (num) for data in a timeseries.

+
    +
  • The first and only argument is a timeseries containing plain num values

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> dkurtosis(_)
+-1.6923313578244437
+
+
+
+
+

sum

+

Added in revision 1

+

Function sum returns the sum of the num values in a timeseries or a dict. +If applied to a timeseries, the result is time-weighted.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> sum(_)
+216
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> sum(d)
+216
+
+
+
+
+

mean

+

Added in revision 1

+

Function mean returns the mean of the num values in a timeseries or a dict. If applied to a timeseries, the result is time-weighted.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> mean(_)
+54 # will be different from dmean if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> mean(d)
+54
+
+
+
+
+

median

+

Added in revision 1

+

Function median returns the median of the num values in a timeseries or a dict. If applied to a timeseries, the result is time-weighted.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> median(_)
+2 # will be different from dmedian if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> median(d)
+2
+
+
+
+
+

percentile

+

Added in revision 1

+

Function percentile returns the time-weighted nth percentile (num) of a timeseries or a dict. +If applied to a timeseries, the result is time-weighted.

+
    +
  • The first argument is a timeseries or a dict containing plain num values

  • +
  • The second argument is a num specifying the percentile. If it is greater than \(100\) or lower than \(0\), the return value will be \(0\).

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> percentile(_, 90)
+200 # will be different from dpercentile if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> percentile(d, 90)
+200
+
+
+
+
+

variance

+

Added in revision 1

+

Function variance returns the statistical variance of the num values in a timeseries or a dict. +If applied to a timeseries, the result is time-weighted.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> variance(_)
+9503.333333333334 # will be different from dvariance if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> variance(d, 90)
+9503.33333333
+
+
+
+
+

stddev

+

Added in revision 1

+

Function stddev returns the standard deviation of the num values in a timeseries or a dict. +If applied to a timeseries, the result is time-weighted.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> stddev(_)
+97.485041588 # will be different from dstddev if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> stddev(d, 90)
+97.485041588
+
+
+
+
+

skew

+

Added in revision 1

+

Function skew returns the skewness of dist.n of the num values in a timeseries or a dict. +If applied to a timeseries, the result is time-weighted. If the timeseries has exactly one element, \(0\) is returned.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> skew(_)
+0.7431002727844832 # will be different from dskew if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> skew(d, 90)
+0.7431002727844832
+
+
+
+
+

kurtosis

+

Added in revision 1

+

Function kurtosis returns the kurtosis of dist.n of the num values in a timeseries or a dict. +If applied to a timeseries, the result is time-weighted. If the timeseries has exactly one element, \(0\) is returned.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        tstamp1: 13
+        tstamp2: 1
+        tstamp3: 2
+        tstamp4: 200
+}
+>>> skew(_)
+0.7431002727844832 # will be different from dskew if space between the timestamps (weight) is not constant
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["key3"] = 2
+>>> d["key4"] = 200
+>>> skew(d, 90)
+0.7431002727844832
+
+
+
+
+

rate

+

Added in revision 1

+

Function rate returns a timeseries of rates computed from the initial timeseriesnum values.

+ +
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+        2019-08-31 00:00:00: 1
+        2019-08-31 00:01:00: 10
+        2019-08-31 00:02:00: 50
+        2019-08-31 00:03:00: 110
+        2019-08-31 00:04:00: 230
+}
+>>> rate(_)
+timeseries{
+        2019-08-31 00:00:00: 0.016666666666666666
+        2019-08-31 00:01:00: 0.15
+        2019-08-31 00:02:00: 0.6666666666666666
+        2019-08-31 00:03:00: 1
+        2019-08-31 00:04:00: 2
+}
+
+
+
+
+

linregression

+

Added in revision 3

+

Function linregression produces a linear fit of a timeseries of num.

+ +

It returns a dict with 4 entries slope, intercept, R2 and fit. The fit entry is a timeseries +with num values corresponding to the fitted line on the input timeseries’ timestamps. The slope +and intercept are in seconds.

+
>>> `analytics:/path/to/data`[5m] | field("numfield")
+timeseries{
+        start: 2021-10-14 13:57:55.000545 +0100 IST
+        end: 2021-10-14 14:02:55.000545 +0100 IST
+        2021-10-14 13:57:00 +0100 IST: 3.801633107494212e-05
+        2021-10-14 13:58:00 +0100 IST: 7.653320559746086e-05
+        2021-10-14 13:59:00 +0100 IST: 3.971200542852744e-05
+        2021-10-14 14:00:00 +0100 IST: 3.981215360110563e-05
+        2021-10-14 14:01:00 +0100 IST: 5.2121957107961934e-05
+        2021-10-14 14:02:00 +0100 IST: 3.838672949594982e-05
+}
+>>> linregression(_)
+dict{
+        R2: 0.06273653863866613
+        fit: timeseries{
+                start: 2021-10-14 13:57:00 +0100 IST
+                end: 2021-10-14 14:02:00 +0100 IST
+                2021-10-14 13:57:00 +0100 IST: 5.252194027605128e-05
+                2021-10-14 13:58:00 +0100 IST: 5.0485322987015024e-05
+                2021-10-14 13:59:00 +0100 IST: 4.844870569797877e-05
+                2021-10-14 14:00:00 +0100 IST: 4.641208840183708e-05
+                2021-10-14 14:01:00 +0100 IST: 4.4375471112800824e-05
+                2021-10-14 14:02:00 +0100 IST: 4.2338853823764566e-05
+        }
+        intercept: 55.47126937459381
+        slope: -3.39436215194667e-08
+}
+
+
+
+
+

ewlinregression

+

Added in revision 3

+

Function ewlinregression produces a linear fit of a timeseries of num using exponentially +decaying weights. The older the value in the timeseries the smaller the weight. The latest value is +always given a weight of \(1\).

+
    +
  • The first argument is the input timeseries of num

  • +
  • The second argument is the desired weight that a point with time x seconds in the past would have

  • +
  • the third argument is how long ago that time \(x\) is, in seconds

  • +
+

It returns a dict with 4 entries slope, intercept, R2 and fit. The fit entry is a timeseries +with num values corresponding to the fitted line on the input timeseries’ timestamps. The slope +and intercept are in seconds.

+
>>> `analytics:/path/to/data`[5m] | field("numfield")
+timeseries{
+        start: 2021-10-14 13:57:55.000545 +0100 IST
+        end: 2021-10-14 14:02:55.000545 +0100 IST
+        2021-10-14 13:57:00 +0100 IST: 3.801633107494212e-05
+        2021-10-14 13:58:00 +0100 IST: 7.653320559746086e-05
+        2021-10-14 13:59:00 +0100 IST: 3.971200542852744e-05
+        2021-10-14 14:00:00 +0100 IST: 3.981215360110563e-05
+        2021-10-14 14:01:00 +0100 IST: 5.2121957107961934e-05
+        2021-10-14 14:02:00 +0100 IST: 3.838672949594982e-05
+}
+>>> ewlinregression(_, 0.01, 100.0)
+dict{
+        R2: 0.34201204121343765
+        fit: timeseries{
+                start: 2021-10-14 14:03:00 +0100 IST
+                end: 2021-10-14 14:08:00 +0100 IST
+                2021-10-14 14:03:00 +0100 IST: 4.5425229615148055e-05
+                2021-10-14 14:04:00 +0100 IST: 4.4509288322558405e-05
+                2021-10-14 14:05:00 +0100 IST: 4.359334702641604e-05
+                2021-10-14 14:06:00 +0100 IST: 4.267740573382639e-05
+                2021-10-14 14:07:00 +0100 IST: 4.176146444123674e-05
+                2021-10-14 14:08:00 +0100 IST: 4.0845523145094376e-05
+        }
+        intercept: 24.94748623552414
+        slope: -1.526568822982724e-08
+}
+
+
+
+
+
+

String manipulation

+
+

strToUpper

+

Added in revision 1

+

Function strToUpper returns uppercase version of given str.

+
    +
  • The first and only parameter is a str to convert to uppercase

  • +
+
>>> strToUpper("ToUpper")
+"TOUPPER"
+
+
+
+
+

strToLower

+

Added in revision 1

+

Function strToLower returns lowercase version of given str.

+
    +
  • The first and only parameter is a str to convert to lowercase

  • +
+
>>> strToLower("ToLower")
+"TOLOWER"
+
+
+
+
+

strContains

+

Added in revision 1

+

Function strContains returns whether the first str contains the second str.

+
    +
  • Both arguments to the function are str

  • +
+
>>> strContains("thatistext", "is")
+true
+
+
+
+
+

strCount

+

Added in revision 1

+

Function strCount returns the number of occurrences of the second str in the first str.

+
    +
  • Both arguments to the function are str

  • +
+
>>> strCount("tertarter", "te")
+2
+
+
+
+
+

strIndex

+

Added in revision 1

+

Function strIndex returns the index of the first occurrence of the second str in the first str, and -1 if it is not present.

+
    +
  • Both arguments to the function are str

  • +
+
>>> strIndex("thatistext", "is")
+4
+
+
+
+
+

strReplace

+

Added in revision 1

+

Function strReplace returns a copy of the first str, where occurrences of the second str are replaced by the third str.

+
    +
  • The three arguments to the function are str

  • +
+
>>> strReplace("thatistext", "is", "was")
+"thatwastext"
+
+
+
+
+

strHasPrefix

+

Added in revision 1

+

Function strHasPrefix returns whether the first str starts with the second str.

+
    +
  • Both arguments to the function are str

  • +
+
>>> strHasPrefix("thatistext", "is")
+false
+>>> strHasPrefix("thatistext", "that")
+true
+
+
+
+
+

strHasSuffix

+

Added in revision 1

+

Function strHasSuffix returns whether the first str ends with the second str.

+
    +
  • Both arguments to the function are str

  • +
+
>>> strHasSuffix("thatistext", "xt")
+true
+
+
+
+
+

strSplit

+

Added in revision 1

+

Function strSplit returns a timeseries of str. The function splits the first str into substrings, separated by the second str.

+
    +
  • Both arguments to the function are str

  • +
+
>>> strSplit("that./is.text", "./")
+timeseries{
+        0000-00-00 00:00:00.000000001: "that"
+        0000-00-00 00:00:00.000000002: "is.text"
+}
+
+
+
+
+

strCut

+

Added in revision 4

+

Function strCut returns the portion of a str between two indexes (excluding ending index).

+

Negative indexes start from the end of the input str.

+
    +
  • The first argument (str) is the string from which the portion is returned

  • +
  • The second argument (num) is the starting index

  • +
  • The third argument (num) is the ending index

  • +
+
>>> strCut("0123456789", 1, 4)
+123
+>>> strCut("0123456789", -8, -2)
+234567
+>>> strCut("abcd", 1, 3)
+bc
+
+
+
+
+

reFindAll

+

Added in revision 1

+

Function reFindAll returns a timeseries of str which contains matches of the second str (regex) in the first str.

+
    +
  • Both arguments to the function are str

  • +
+
>>> reFindAll("i am a string with text", "i[a-z]+")
+timeseries{
+        0000-00-00 00:00:00.000000001: "ing"
+        0000-00-00 00:00:00.000000002: "ith"
+}
+
+
+
+
+

reMatch

+

Added in revision 1

+

Function reMatch returns whether the first str contains matches of the second str (regex).

+
    +
  • Both arguments to the function are str

  • +
+
>>> reMatch("i am a string with text", "i[a-z]+")
+true
+
+
+
+
+

reFindCaptures

+

Added in revision 1

+

Function reFindCaptures returns a timeseries of str lists. Each list contains the full match followed by each capture

+
    +
  • Both arguments to the function are str. The first one is the str to match, and the second is the regular expression

  • +
+
>>> reFindCaptures("foobarbaztootartaz", "foo")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["foo"]
+}
+>>> reFindCaptures("foobarbaztootartaz", "(foo)")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["foo", "foo"]
+}
+>>> reFindCaptures("foobarbaztootartaz", "f(oo)")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["foo", "oo"]
+}
+>>> reFindCaptures("foobarbaztootartaz", "(oo)")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["oo", "oo"]
+        0000-00-00 00:00:00.000000002: ["oo", "oo"]
+}
+>>> reFindCaptures("foobarbaztootartaz", "[ft](oo)")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["foo", "oo"]
+        0000-00-00 00:00:00.000000002: ["too", "oo"]
+}
+>>> reFindCaptures("foobarbaztootartaz", "([ft])(oo)")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["foo", "f", "oo"]
+        0000-00-00 00:00:00.000000002: ["too", "t", "oo"]
+}
+>>> reFindCaptures("foobarbaztootartaz", "[ft](oo).*(az)")
+timeseries{
+        0000-00-00 00:00:00.000000001: ["foobarbaztootartaz", "oo", "az"]
+}
+
+
+
+
+
+

CLI-only Functions

+
+

Warning

+

The functions described in this section can only be used in CLI. They cannot be called from a service +or through a Web interface.

+
+
+

help

+

Added in revision 1

+

Function help returns the help of all filters and functions as a formatted str.

+
>>> help()
+# Functions
+## now
+- Function `now` returns the current time. Time is constant across all the script, so that all operations and queries have the same reference time
+[...]
+
+
+
+
+

dump

+

Added in revision 1

+

Function dump attempts to dump variables from the interpreter into a file.

+
    +
  • The first and only argument is the path to the file (str)

  • +
+
>>> let myVar = 2
+>>> dump("file.dump")
+
+
+
+
+

load

+

Added in revision 1

+

Function load attempts to load variables into the interpreter from a file.

+
    +
  • The first and only argument is the path to the file (str)

  • +
+
>>> load("file.dump")
+true
+>>> myVar
+2
+>>> let myVar = 5
+>>> if load("file.dump") {
+...     myVar
+... }
+2
+
+
+
+
+

plot

+

Added in revision 1

+

Function plot plots a timeseries or dict of num values.

+
    +
  • The first argument is the timeseries or dict of num values to plot

  • +
  • The second argument (optional) is the path (str) to the image PNG file to write the plot to. If not specified, defaults to plot.png

  • +
+
>>> plot(myTimeseriesOrDict, "myplotimg.png")
+
+
+
+
+
+
+

Filters

+
+

field

+

Added in revision 1

+

Filter field applies to a timeseries of dicts. Returns a timeseries of the value at the +specified key for each entry of the timeseries that contains this field. Entries that don’t +contain the key are not in the output timeseries.

+
    +
  • The filtered value is a timeseries of dicts

  • +
  • The first and only parameter is the key to keep in the dicts

  • +
+
>>> `analytics:/path/to/data`[1]
+timeseries{
+    tstamp1: dict{key1: val1, key2: val2, key3: val3}
+    tstamp2: dict{key1: val4, key2: val5}
+}
+>>> _ | field("key1")
+timeseries{
+    tstamp1: val1
+    tstamp2: val4
+}
+
+
+
+
+

fields

+

Added in revision 3

+

Filter fields applies to a dict. It filters it and returns a new dict containing only the +key/value pairs whose key is passed as parameter.

+
    +
  • The filtered value is a dict

  • +
  • There is a variable (\(0\) to \(n\)) number of parameters: they are the keys to keep in the output dict

  • +
+
+

Note

+

If a specified key is missing from the source dict, the filter will not fail but the +output dict will also be missing that key

+
+
>>> let d = `analytics:/path/to/data/*` | map(merge(_value))
+>>> d
+dict{
+    key0: dict{key1: val1, key2: val2, key3: val3}
+    key01: dict{key1: val4, key2: val5}
+    key02: dict{key1: val6, key2: val7}
+}
+>>> d | fields("key0", "key01")
+dict{
+    key0: dict{key1: val1, key2: val2, key3: val3}
+    key01: dict{key1: val4, key2: val5}
+}
+>>> d | fields("k")
+dict{
+}
+>>> d | fields()
+dict{
+}
+>>> d | fields("key01") | map(_value | fields("key2"))
+dict{
+    key01: dict{key2: val5}
+}
+
+
+
+
+

setFields

+

Added in revision 3

+

Filter setFields sets some key/value pairs in a dict. If the key already existed in the +filtered dict, its value will be replaced with the new one in the output dict (like all filters, +setFields returns a filtered copy of the dict and does not alter the source). If the key did not +exist in the filtered dict, the key/value pair will just be added to the output dict.

+
    +
  • The filtered value is a dict

  • +
  • There is a variable (0 to n) even number of parameters: they correspond to the list of key/value +pairs

  • +
+
>>> let d = newDict() | setFields("k1", "v1", "k2", 2.3, "k3", 3)
+>>> d
+dict{
+    k1: v1
+    k2: 2.3
+    k3: 3
+}
+>>> d | setFields("k4", newDict() | setFields("k5", "v5"))
+dict{
+    k1: v1
+    k2: 2.3
+    k3: 3
+    k4: dict{
+        k5: v5
+    }
+}
+>>> d
+dict{
+    k1: v1
+    k2: 2.3
+    k3: 3
+} # the source dict is not altered
+
+
+
+
+

applyDeletes

+

Added in revision 4

+

Filter applyDeletes applies the deletes to a timeseries. This timeseries must be freshly returned by a query. +Most filters remove the deletes information from timeseries, so this should be called before any other filter or function.

+

If no argument is passed, applying a delete will remove all entries with that delete’s key that were updated prior to the delete +itself. This use-case is mostly appropriate when used with the result of a query that does not contain historical data (state-only). +With historical data, this would wipe deleted entries from ever having existed in the timeseries, instead of signaling the end +of the entry at the moment of deletion.

+

If an argument is passed, then the expression defines a value that will be written at the moment of the delete for that key. +This use-case is more appropriate with historical data because it will not remove entries, but instead create an entry that signals +the end of the value.

+
    +
  • The filtered value is a timeseries that still contains delete information.

  • +
  • The only and optional parameter is the expression. Its value can be of any type after evaluation.

  • +
+

Usable metavariables in the expression are:

+
+
    +
  • _key or _1: key matching the delete

  • +
  • _index: index of the delete in the timeseries

  • +
  • _updindex: index of the last update for this key in the timeseries

  • +
  • _time or _2: time of the delete in the timeseries

  • +
  • _updtime: time of the last update for this key in the timeseries

  • +
  • _value or _3: last value prior to the delete for the deleted key

  • +
  • _src or _4: reference to the timeseries being filtered

  • +
+
+
+Example
>>> let a = `analytics:/tags/BugAlerts/Query/gNMIEnabled`[5]
+>>> a
+timeseries{
+    start: 2021-03-17 02:48:58.205235103 +0100 CET
+    end: 2022-10-19 13:30:33.722908 +0200 CEST
+    2021-03-17 02:48:58.205235103 +0100 CET: dict{
+        JAS12200014: true
+        JAS16040045: true
+        JAS17250006: true
+        JAS17250010: true
+        JAS17510146: true
+        JPE14171444: true
+        JPE17191574: true
+        SSJ17371234: true
+    }
+    2021-05-12 17:32:58.269740014 +0200 CEST: dict{
+        HSH14075043: true
+        HSH14075051: true
+    }
+    2021-11-03 17:09:46.753872494 +0100 CET: dict{
+        HSH14280171: true
+        HSH14420467: true
+        JPE14250224: true
+        JPE14383408: true
+        SSJ17049015: true
+        SSJ17374660: true
+    }
+    2021-11-11 05:09:22.273451668 +0100 CET: dict{
+        JAS14170008: true
+        JAS14210057: true
+        JAS17070003: true
+        JAS18170075: true
+        JPE14120478: true
+        JPE19280519: true
+    }
+    2022-02-18 23:08:10.204460235 +0100 CET: dict{
+        2568DB4A33177968A78C4FD5A8232159: true
+        6323DA7D2B542B5D09630F87351BEA41: true
+        BAD032986065E8DC14CBB6472EC314A6: true
+        CD0EADBEEA126915EA78E0FB4DC776CA: true
+    }
+    2022-02-22 00:48:45.347884243 +0100 CET: dict{0123F2E4462997EB155B7C50EC148767: true}
+    2022-07-18 18:10:07.772750473 +0200 CEST: dict{JPE20244151: true}
+}
+>>> deletes(a)
+timeseries{
+    start: 2021-03-17 02:48:58.205235103 +0100 CET
+    end: 2022-10-19 13:30:33.722908 +0200 CEST
+    2021-11-23 11:09:21.716099165 +0100 CET: dict{
+        HSH14075043: <nil>
+        HSH14075051: <nil>
+        HSH14280171: <nil>
+        HSH14420467: <nil>
+        JAS14170008: <nil>
+        JAS14210057: <nil>
+        JAS16040045: <nil>
+        JAS17070003: <nil>
+        JAS17250006: <nil>
+        JAS17250010: <nil>
+        JAS17510146: <nil>
+        JAS18170075: <nil>
+        JPE14120478: <nil>
+        JPE14171444: <nil>
+        JPE14250224: <nil>
+        JPE14383408: <nil>
+        JPE17191574: <nil>
+        JPE19280519: <nil>
+        SSJ17049015: <nil>
+        SSJ17371234: <nil>
+        SSJ17374660: <nil>
+    }
+    2022-02-22 00:48:45.347884243 +0100 CET: dict{
+        2568DB4A33177968A78C4FD5A8232159: <nil>
+        6323DA7D2B542B5D09630F87351BEA41: <nil>
+        BAD032986065E8DC14CBB6472EC314A6: <nil>
+        CD0EADBEEA126915EA78E0FB4DC776CA: <nil>
+    }
+}
+>>> a | applyDeletes()
+timeseries{
+    start: 2021-03-17 02:48:58.205235103 +0100 CET
+    end: 2022-10-19 13:30:33.722908 +0200 CEST
+    2021-03-17 02:48:58.205235103 +0100 CET: dict{JAS12200014: true}
+    2022-02-22 00:48:45.347884243 +0100 CET: dict{0123F2E4462997EB155B7C50EC148767: true}
+    2022-07-18 18:10:07.772750473 +0200 CEST: dict{JPE20244151: true}
+}
+>>> a | applyDeletes(_key+" is deleted, its value was " + str(_value))
+timeseries{
+    start: 2021-03-17 02:48:58.205235103 +0100 CET
+    end: 2022-10-19 13:30:33.722908 +0200 CEST
+    2021-03-17 02:48:58.205235103 +0100 CET: dict{
+        JAS12200014: true
+        JAS16040045: true
+        JAS17250006: true
+        JAS17250010: true
+        JAS17510146: true
+        JPE14171444: true
+        JPE17191574: true
+        SSJ17371234: true
+    }
+    2021-05-12 17:32:58.269740014 +0200 CEST: dict{
+        HSH14075043: true
+        HSH14075051: true
+    }
+    2021-11-03 17:09:46.753872494 +0100 CET: dict{
+        HSH14280171: true
+        HSH14420467: true
+        JPE14250224: true
+        JPE14383408: true
+        SSJ17049015: true
+        SSJ17374660: true
+    }
+    2021-11-11 05:09:22.273451668 +0100 CET: dict{
+        JAS14170008: true
+        JAS14210057: true
+        JAS17070003: true
+        JAS18170075: true
+        JPE14120478: true
+        JPE19280519: true
+    }
+    2021-11-23 11:09:21.716099165 +0100 CET: dict{
+        HSH14075043: HSH14075043 is deleted, its value was true
+        HSH14075051: HSH14075051 is deleted, its value was true
+        HSH14280171: HSH14280171 is deleted, its value was true
+        HSH14420467: HSH14420467 is deleted, its value was true
+        JAS14170008: JAS14170008 is deleted, its value was true
+        JAS14210057: JAS14210057 is deleted, its value was true
+        JAS16040045: JAS16040045 is deleted, its value was true
+        JAS17070003: JAS17070003 is deleted, its value was true
+        JAS17250006: JAS17250006 is deleted, its value was true
+        JAS17250010: JAS17250010 is deleted, its value was true
+        JAS17510146: JAS17510146 is deleted, its value was true
+        JAS18170075: JAS18170075 is deleted, its value was true
+        JPE14120478: JPE14120478 is deleted, its value was true
+        JPE14171444: JPE14171444 is deleted, its value was true
+        JPE14250224: JPE14250224 is deleted, its value was true
+        JPE14383408: JPE14383408 is deleted, its value was true
+        JPE17191574: JPE17191574 is deleted, its value was true
+        JPE19280519: JPE19280519 is deleted, its value was true
+        SSJ17049015: SSJ17049015 is deleted, its value was true
+        SSJ17371234: SSJ17371234 is deleted, its value was true
+        SSJ17374660: SSJ17374660 is deleted, its value was true
+    }
+    2022-02-18 23:08:10.204460235 +0100 CET: dict{
+        2568DB4A33177968A78C4FD5A8232159: true
+        6323DA7D2B542B5D09630F87351BEA41: true
+        BAD032986065E8DC14CBB6472EC314A6: true
+        CD0EADBEEA126915EA78E0FB4DC776CA: true
+    }
+    2022-02-22 00:48:45.347884243 +0100 CET: dict{
+        2568DB4A33177968A78C4FD5A8232159: 2568DB4A33177968A78C4FD5A8232159 is deleted, its value was true
+        6323DA7D2B542B5D09630F87351BEA41: 6323DA7D2B542B5D09630F87351BEA41 is deleted, its value was true
+        BAD032986065E8DC14CBB6472EC314A6: BAD032986065E8DC14CBB6472EC314A6 is deleted, its value was true
+        CD0EADBEEA126915EA78E0FB4DC776CA: CD0EADBEEA126915EA78E0FB4DC776CA is deleted, its value was true
+    }
+    2022-02-22 00:48:45.347884243 +0100 CET: dict{0123F2E4462997EB155B7C50EC148767: true}
+    2022-07-18 18:10:07.772750473 +0200 CEST: dict{JPE20244151: true}
+}
+
+
+
+
+

renameFields

+

Added in revision 3

+

Filter renameFields renames some keys in a dict. The keys which are not specified in the +arguments will be kept in the output dict. Use the fields filter to remove them.

+
    +
  • The filtered value is a dict

  • +
  • There is a variable (\(0\) to \(n\)) even number of parameters: they correspond to the list of old-key/new-key pairs

  • +
+
+

Note

+

If a specified key is missing from the source dict, the filter will not fail and that pair +will just be ignored

+
+
>>> let d = `analytics:/path/to/data/*` | map(merge(_value))
+>>> d
+dict{
+    key0: dict{key1: val1, key2: val2, key3: val3}
+    key01: dict{key1: val4, key2: val5}
+}
+>>> d | renameFields("key0", "newkey0")
+dict{
+    newkey0: dict{key1: val1, key2: val2, key3: val3}
+    key01: dict{key1: val4, key2: val5}
+}
+>>> d | renameFields("key0", "newkey0", "key01", "newkey01")
+dict{
+    newkey0: dict{key1: val1, key2: val2, key3: val3}
+    newkey01: dict{key1: val4, key2: val5}
+}
+>>> d | fields("key01") | map(_value | renameFields("key2", "newkey2"))
+dict{
+    key01: dict{key1: val4, newkey2: val5}
+}
+
+
+
+
+

where

+

Added in revision 1

+

Filter where returns a filtered timeseries or dict containing exclusively the entries of the input where +the predicate passed as parameter is true.

+
    +
  • The first and only parameter is the predicate. It is an expression, the value of which must be a boolean after evaluation.

  • +
+

Usable metavariables in the predicate for timeseries are:

+
+
    +
  • _index or _1: index of the current element (starting at \(0\))

  • +
  • _time or _2: timestamp of the current element

  • +
  • _value or _3: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
+

Usable metavariables in the predicate for dicts are:

+
+
    +
  • _key or _1: key of the current element

  • +
  • _value or _2: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
+
>>> `analytics:/path/to/data`[3] | field("key1")
+timeseries{
+    tstamp1: 1
+    tstamp2: 2
+    tstamp3: 3
+    tstamp4: 4
+}
+>>> _ | where(_value >= 3)
+timeseries{
+    tstamp3: 3
+    tstamp4: 4
+}
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["k3"] = 1
+>>> d["k4"] = 1
+>>> d | where(strContains(_key, "key"))
+dict{
+    "key1": 13
+    "key2": 1
+}
+
+
+
+
+

map

+

Added in revision 1

+

Filter map returns a timeseries or dict containing the results of the expression passed as parameter applied to each entry of the filtered timeseries or dict.

+
    +
  • The first and only parameter is the expression. Its value can be of any type after evaluation.

  • +
+

Usable metavariables in the expression for timeseries are:

+
    +
  • _index or _1: index of the current element (starting at \(0\))

  • +
  • _time or _2: timestamp of the current element

  • +
  • _value or _3: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+

Usable metavariables in the expression for dicts are:

+
    +
  • _key or _1: key of the current element

  • +
  • _value or _2: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
>>> `analytics:/path/to/data`[3]
+timeseries{
+    tstamp1: dict{key1: 1, key2: 12, key3: 11}
+    tstamp2: dict{key1: 2, key2: 123}
+    tstamp3: dict{key1: 3, key2: 78, key3: 42}
+    tstamp4: dict{key1: 4, key2: 68}
+}
+>>> _ | map(_value["key1"] + 1)
+timeseries{
+    tstamp1: 2
+    tstamp2: 3
+    tstamp3: 4
+    tstamp4: 5
+}
+>>> let d = newDict()
+>>> d["key1"] = 13
+>>> d["key2"] = 1
+>>> d["k3"] = 1
+>>> d["k4"] = 1
+>>> d | map(_key + "l")
+dict{
+    "key1": key1l
+    "key2": key2l
+    "k3": k3l
+    "k4": k4l
+}
+>>> d | map(_value^2)
+dict{
+    "key1": 169
+    "key2": 1
+    "k3": 1
+    "k4": 1
+}
+
+
+
+
+

mapne

+

Added in revision 1

+

Filter mapne (map-not-empty) returns a timeseries or dict containing the results of the +expression passed as first parameter applied to the result of the expression passed as second parameter +if its result is not empty. This applies to each entry of the filtered timeseries or dict.

+
    +
  • The first parameter is the main expression. Its value can be of any type after evaluation.

    +

    Usable metavariables in the expression for timeseries are:

    +
    +
      +
    • _index or _1: index of the current element (starting at \(0\))

    • +
    • _time or _2: timestamp of the current element

    • +
    • _value or _3: result of the second expression applied to the current element

    • +
    • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

    • +
    +
    +

    Usable metavariables in the expression for :ref:`dict`s are:

    +
    +
      +
    • _key or _1: key of the current element

    • +
    • _value or _2: result of the second expression applied to the current element

    • +
    • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

    • +
    +
    +
  • +
  • the second parameter is the filtering expression. Its value can be of any type after evaluation.

    +

    Usable metavariables in the expression for timeseries are:

    +
    +
      +
    • _index or _1: index of the current element (starting at 0)

    • +
    • _time or _2: timestamp of the current element

    • +
    • _value or _3: value of the current element

    • +
    • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

    • +
    +
    +

    Usable metavariables in the expression for dicts are:

    +
    +
      +
    • _key or _1: key of the current element

    • +
    • _value or _3 value of the current element

    • +
    • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

    • +
    +
    +
  • +
+
>>> `analytics:/path/to/*/data/with/wildcard`[3]
+dict {
+    pathElement1: timeseries{t1:1, t2:2, t3:3, t4:4}
+    pathElement2: timeseries{t5:5, t6:6, t7:7, t8:8}
+    pathElement3: timeseries{}
+    pathElement4: timeseries{t9:9, t10:10, t11:11, t12:12}
+}
+>>> _ | map(mean(_value))
+error: cannot compute mean of empty timeseries
+>>> _ | mapne(mean(_value), _value)
+dict {
+    pathElement1: 2.5
+    pathElement2: 6.5
+    pathElement4: 10.5
+}
+>>> `analytics:/path/to/data`[3]
+timeseries{
+    tstamp1: dict{k1:1, k2:2, k3:3, k4:4}
+    tstamp2: dict{k1:1, k2:2, k3:3, k4:4}
+    tstamp3: dict{}
+    tstamp4: dict{k1:1, k2:2, k3:3, k4:4}
+}
+>>> _ | map(mean(_value))
+error: cannot compute mean of empty dict
+>>> _ | mapne(mean(_value) + 12, _value)
+timeseries{
+    tstamp1: 14.5
+    tstamp2: 14.5
+    tstamp4: 14.5
+}
+
+
+
+
+

recmap

+

Added in revision 1

+

Filter recmap returns a timeseries or dict containing the results of the expression passed as parameter applied to each +entry of the filtered timeseries or dict, at the specified depth.

+
    +
  • The first parameter is the recursion depth (num).

  • +
  • The second parameter is the expression. Its value can be of any type after evaluation.

  • +
+

Usable metavariables in the expression for timeseries are:

+
    +
  • _index or _1: index of the current element (starting at \(0\))

  • +
  • _time or _2: timestamp of the current element

  • +
  • _value or _3: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+

Usable metavariables in the expression for dicts are:

+
    +
  • _key or _1: key of the current element

  • +
  • _value or _2: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
>>> `analytics:/path/to/*/data/with/*/2/wildcards`
+dict {
+    pe1: dict{pe1.1: timeseries{1, 2, 3}, pe1.2: timeseries{1, 2, 3}}
+    pe2: dict{pe2.1: timeseries{1, 2, 3}, pe2.2: timeseries{1, 2, 3}}
+    pe3: dict{pe3.1: timeseries{1, 2, 3}, pe3.2: timeseries{1, 2, 3}}
+} # we want the same recursion depth for every branch here, and stop at the timeseries level
+>>> let data = _
+>>> data | map(_value | map(mean(_value)))
+dict {
+    pe1: dict{pe1.1: 2, pe1.2: 2}
+    pe2: dict{pe2.1: 2, pe2.2: 2}
+    pe3: dict{pe3.1: 2, pe3.2: 2}
+} # nested map filters work but are very verbose
+>>> data | recmap(2, mean(_value))
+dict {
+    pe1: dict{pe1.1: 2, pe1.2: 2}
+    pe2: dict{pe2.1: 2, pe2.2: 2}
+    pe3: dict{pe3.1: 2, pe3.2: 2}
+}
+# recmap is much clearer.
+
+
+
+
+

topK

+

Added in revision 3

+

Filter topK filters the collection to keep only the k highest values. This filter can be +applied to a timeseries or a dict.

+
    +
  • The first parameter is the k parameter, which is the number of values to keep in the filtered collection.

  • +
  • The second parameter is an expression that returns for each entry of the collection the value to compare. +The return type of this expression must be comparable (num, str, time, or duration)

  • +
+

Usable metavariables in the expression for timeseries are:

+
    +
  • _index or _1: index of the current element (starting at \(0\))

  • +
  • _time or _2: timestamp of the current element

  • +
  • _value or _3: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+

Usable metavariables in the expression for dicts are:

+
    +
  • _key or _1: key of the current element

  • +
  • _value or _2: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
>>> let data = `analytics:/path/to/some/*/data` | map(merge(_value))
+>>> data
+dict{
+    Ethernet49/1: dict{
+        in: 11.845057565692196
+        out: 20.816078774499992
+    }
+    Ethernet49/5: dict{
+        in: 4.021321282808746
+        out: 8.868898231943206
+    }
+    Ethernet51/1: dict{
+        in: 2.1800167411644353
+        out: 2.413745251460854
+    }
+    Ethernet51/2: dict{
+        in: 3.126216167169341
+        out: 26.05024018915018
+    }
+    Ethernet51/3: dict{
+        in: 54.1046901332212
+        out: 5.035469519006775
+    }
+    Ethernet51/4: dict{
+        in: 7.313228804713885
+        out: 4.899238295809337
+    }
+    Ethernet8: dict{
+        in: 0
+        out: 71.6547381850231
+    }
+    Management1: dict{
+        in: 6.139184309225689
+        out: 0.7010378175218949
+    }
+    Port-Channel512: dict{
+        in: 7.864572656164906
+        out: 14.724350983923758
+    }
+    Port-Channel532: dict{
+        in: 16.652391153117858
+        out: 9.562088032011452
+    }
+}
+>>> data | topK(2, _value["in"])
+dict{
+    Ethernet51/3: dict{
+        in: 54.1046901332212
+        out: 5.035469519006775
+    }
+    Port-Channel532: dict{
+        in: 16.652391153117858
+        out: 9.562088032011452
+    }
+}
+>>> data | map(_value["in"]) | topK(2, _value)
+dict{
+    Ethernet51/3: 54.1046901332212
+    Port-Channel532: 16.652391153117858
+}
+
+
+
+
+

bottomK

+

Added in revision 3

+

Filter bottomK filters the collection to keep only the k lowest values. This filter can be +applied to a timeseries or a dict.

+
    +
  • The first parameter is the k parameter, which is the number of values to keep in the filtered collection.

  • +
  • The second parameter is an expression that returns for each entry of the collection the value to compare. +The return type of this expression must be comparable (num, str, time, or duration)

  • +
+

Usable metavariables in the expression for timeseries are:

+
    +
  • _index or _1: index of the current element (starting at \(0\))

  • +
  • _time or _2: timestamp of the current element

  • +
  • _value or _3: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+

Usable metavariables in the expression for dicts are:

+
    +
  • _key or _1: key of the current element

  • +
  • _value or _2: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
>>> let data = `analytics:/path/to/some/*/data` | map(merge(_value))
+>>> data
+dict{
+    Ethernet49/1: dict{
+        in: 11.845057565692196
+        out: 20.816078774499992
+    }
+    Ethernet49/5: dict{
+        in: 4.021321282808746
+        out: 8.868898231943206
+    }
+    Ethernet51/1: dict{
+        in: 2.1800167411644353
+        out: 2.413745251460854
+    }
+    Ethernet51/2: dict{
+        in: 3.126216167169341
+        out: 26.05024018915018
+    }
+    Ethernet51/3: dict{
+        in: 54.1046901332212
+        out: 5.035469519006775
+    }
+    Ethernet51/4: dict{
+        in: 7.313228804713885
+        out: 4.899238295809337
+    }
+    Ethernet8: dict{
+        in: 0
+        out: 71.6547381850231
+    }
+    Management1: dict{
+        in: 6.139184309225689
+        out: 0.7010378175218949
+    }
+    Port-Channel512: dict{
+        in: 7.864572656164906
+        out: 14.724350983923758
+    }
+    Port-Channel532: dict{
+        in: 16.652391153117858
+        out: 9.562088032011452
+    }
+}
+>>> data | bottomK(2, _value["in"])
+dict{
+    Ethernet51/1: dict{
+        in: 2.1800167411644353
+        out: 2.413745251460854
+    }
+    Ethernet8: dict{
+        in: 0
+        out: 71.6547381850231
+    }
+}
+>>> data | map(_value["in"]) | bottomK(2, _value)
+dict{
+    Ethernet51/1: 2.1800167411644353
+    Ethernet8: 0
+}
+
+
+
+
+

deepmap

+

Added in revision 1

+

Filter deepmap returns a timeseries or dict containing the results of the expression +passed as parameter applied to each entry of the filtered timeseries or dict, which can +contain nested timeseries or dicts.

+
    +
  • The first and only parameter is the expression. Its value can be of any type after evaluation.

  • +
  • Metavariables are applicable to the collection containing the leaf node to which the expression is applied, which can be nested under several layers.

  • +
+

Usable metavariables in the expression for timeseries are:

+
    +
  • _index or _1: index of the current element (starting at \(0\))

  • +
  • _time or _2: timestamp of the current element

  • +
  • _value or _3: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+

Usable metavariables in the expression for dicts are:

+
    +
  • _key or _1: key of the current element

  • +
  • _value or _2: value of the current element

  • +
  • _src or _4 (revision 4+): reference to the timeseries or dict being filtered

  • +
+
>>> `analytics:/path/to/*/data/with/wildcard`[3]
+dict {
+    pathElement1: timeseries{t1:1, t2:2, t3:3, t4:4}
+    pathElement2: timeseries{t5:5, t6:6, t7:7, t8:8}
+    pathElement3: timeseries{dict{k10:10}, dict{k11:11}}
+}
+>>> _ | deepmap(_value + 1)
+dict {
+    pathElement1: timeseries{t1:2, t2:3, t3:4, t4:5}
+    pathElement2: timeseries{t5:6, t6:7, t7:8, t8:9}
+    pathElement3: timeseries{dict{k10:11}, dict{k11:12}}
+} # recursion depth can be different between branches, deepmap will recurse as long as the value is either a dict or timeseries
+
+
+
+
+

resample

+

Added in revision 1

+

Filter resample returns a timeseries resampled with the given duration as constant interval. +CloudVision timeseries are state-based, so any value in the output timeseries will be the latest +value prior to the output timestamp in the original timeseries.

+
    +
  • The first and only parameter, of type duration, specifies the interval of the output timeseries.

  • +
+
>>> `analytics:/path/to/data`[3] | field("numfield")
+timeseries{
+    2019-08-31 00:00:00: 13
+    2019-08-31 00:06:23: 1
+    2019-08-31 00:08:29: 2
+    2019-08-31 00:11:43: 200
+}
+>>> _ | resample(2m)
+timeseries{
+    2019-08-31 00:00:00: 13
+    2019-08-31 00:02:00: 13
+    2019-08-31 00:04:00: 13
+    2019-08-31 00:06:00: 13
+    2019-08-31 00:08:00: 13
+    2019-08-31 00:10:00: 2
+    2019-08-31 00:12:00: 200
+}
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 0000000..8776e93 Binary files /dev/null and b/objects.inv differ diff --git a/search.html b/search.html new file mode 100644 index 0000000..a378a7f --- /dev/null +++ b/search.html @@ -0,0 +1,127 @@ + + + + + + Search — CloudVision Advanced Query Language 4 documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2023, Arista Networks.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 0000000..f927f89 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"/mnt/flash usage": [[26, "mnt-flash-usage"], [29, "mnt-flash-usage"]], "7020R Switches": [[26, "r-switches"], [30, "r-switches"]], "7050X3 Switches": [[26, "x3-switches"], [30, "x3-switches"]], "7280R2 Switches": [[26, "r2-switches"], [30, "r2-switches"]], "AQL Documentation": [[34, null]], "AQL Examples": [[36, null]], "AQL Standard Library": [[37, null]], "Additions and Subtractions": [[3, "additions-and-subtractions"], [35, "additions-and-subtractions"]], "Admin state of interfaces per device": [[4, null], [8, "admin-state-of-interfaces-per-device"]], "BGP Historical state tracker": [[16, "bgp-historical-state-tracker"], [20, "bgp-historical-state-tracker"]], "BGP Session Details in the Default VRF for all Devices": [[13, "bgp-session-details-in-the-default-vrf-for-all-devices"], [16, "bgp-session-details-in-the-default-vrf-for-all-devices"]], "BGP Session Status": [[13, "bgp-session-status"], [16, "bgp-session-status"]], "BGP Sessions Flaps": [[16, "bgp-sessions-flaps"], [20, "bgp-sessions-flaps"]], "BGP Sessions that are Not Established": [[13, "bgp-sessions-that-are-not-established"], [16, "bgp-sessions-that-are-not-established"]], "BGP States": [[13, null], [16, "bgp-states"]], "BGP Summary": [[16, "bgp-summary"], [20, "bgp-summary"]], "BGP Syslog Messages": [[16, "bgp-syslog-messages"], [20, "bgp-syslog-messages"]], "Basic statements": [[3, "basic-statements"], [35, "basic-statements"]], "Boolean operations": [[3, "boolean-operations"], [35, "boolean-operations"]], "Boolean value": [[3, "boolean-value"], [35, "boolean-value"]], "CLI-only Functions": [[2, "cli-only-functions"], [37, "cli-only-functions"]], "CPU Utilization in the Fabric": [[26, "cpu-utilization-in-the-fabric"], [29, "cpu-utilization-in-the-fabric"]], "Capacity Planning Routing and Switching": [[14, null], [16, "capacity-planning-routing-and-switching"]], "Change log": [[0, "change-log"]], "Comparisons": [[3, "comparisons"], [35, "comparisons"]], "Complex path elements": [[3, "complex-pathelts"], [35, "complex-pathelts"]], "Contents:": [[34, null], [36, null]], "Count interfaces with non-zero outbound OR inbound traffic (with key existence check)": [[5, null], [8, "count-interfaces-with-non-zero-outbound-or-inbound-traffic-with-key-existence-check"]], "Count interfaces with non-zero outbound traffic": [[6, null], [8, "count-interfaces-with-non-zero-outbound-traffic"]], "Data Analysis Functions": [[2, "data-analysis-functions"], [37, "data-analysis-functions"]], "Dataset": [[3, "dataset"], [35, "dataset"]], "Default behaviour": [[3, "default-behaviour"], [35, "default-behaviour"]], "Dict": [[3, "id14"], [35, "id14"]], "Dicts functions": [[2, "dicts-functions"], [37, "dicts-functions"]], "Directives": [[3, "directives"], [35, "directives"]], "Duration": [[3, "id12"], [35, "id12"]], "EVPN Gateways Numbers (7280R2)": [[14, "evpn-gateways-numbers-7280r2"], [16, "evpn-gateways-numbers-7280r2"]], "EoL Planning": [[22, null], [26, "eol-planning"]], "Fan Status per Device": [[24, "fan-status-per-device"], [26, "fan-status-per-device"]], "Filters": [[3, "id17"], [35, "id17"], [37, "filters"]], "Fixed timestamps range": [[3, "fixed-timestamps-range"], [35, "fixed-timestamps-range"]], "For loop": [[3, "for-loop"], [35, "for-loop"]], "Functions": [[3, "functions"], [35, "functions"], [37, "functions"]], "General functions": [[2, null], [37, "general-functions"]], "Hardware Health Check": [[24, null], [26, "hardware-health-check"]], "High Density Leaf Switches Numbers (7050X3) Tagged with HDL Label": [[14, "high-density-leaf-switches-numbers-7050x3-tagged-with-hdl-label"], [16, "high-density-leaf-switches-numbers-7050x3-tagged-with-hdl-label"]], "IGMP Snooping Table": [[15, null], [16, "igmp-snooping-table"]], "If/Else": [[3, "if-else"], [35, "if-else"]], "Important Pod Count": [[25, null], [26, "important-pod-count"]], "Interface Information wtih a filter on Port Description": [[7, null], [8, "interface-information-wtih-a-filter-on-port-description"]], "Interface States Examples": [[8, null]], "Interface counters sum per interface list per device": [[8, "interface-counters-sum-per-interface-list-per-device"], [9, null]], "LANZ queue Size information filtering the null-values": [[8, "lanz-queue-size-information-filtering-the-null-values"], [10, null]], "Language Operators": [[3, "language-operators"], [35, "language-operators"]], "Language Specification": [[35, null]], "Language keywords": [[3, "language-keywords"], [35, "language-keywords"]], "Language revisions / Change log": [[0, null]], "Layer 2 and Layer 3 examples": [[16, null]], "List of Configured VLANs per VRFs": [[16, "list-of-configured-vlans-per-vrfs"], [19, null]], "List the serial numbers for all transceivers": [[26, "list-the-serial-numbers-for-all-transceivers"], [32, null]], "List the transceiver serial numbers that match the input regex": [[26, "list-the-transceiver-serial-numbers-that-match-the-input-regex"], [33, null]], "List value": [[3, "list-value"], [35, "list-value"]], "Listing Devices that have a file in /var/core": [[26, "listing-devices-that-have-a-file-in-var-core"], [31, null]], "Listing of devices affected by FN44": [[21, null], [26, "listing-of-devices-affected-by-fn44"]], "Listing of devices affected by FN72": [[23, null], [26, "listing-of-devices-affected-by-fn72"]], "Loops": [[3, "loops"], [35, "loops"]], "Low Density Leaf Switches Numbers (7020R) Tagged with the LDL Label": [[14, "low-density-leaf-switches-numbers-7020r-tagged-with-the-ldl-label"], [16, "low-density-leaf-switches-numbers-7020r-tagged-with-the-ldl-label"]], "Manual user input": [[3, "manual-user-input"], [35, "manual-user-input"]], "Map value": [[3, "map-value"], [35, "map-value"]], "Math functions": [[2, "math-functions"], [37, "math-functions"]], "Max Size of the Floodlist": [[25, "max-size-of-the-floodlist"], [26, "max-size-of-the-floodlist"]], "Memory Usage in the Fabric": [[26, "memory-usage-in-the-fabric"], [29, "memory-usage-in-the-fabric"]], "Modulo": [[3, "modulo"], [35, "modulo"]], "Multiplications and Divisions": [[3, "multiplications-and-divisions"], [35, "multiplications-and-divisions"]], "NTP Stats": [[26, "ntp-stats"], [27, null]], "Named wildcards and per-value execution": [[3, "named-wildcards-and-per-value-execution"], [35, "named-wildcards-and-per-value-execution"]], "No parameter": [[3, "no-parameter"], [35, "no-parameter"]], "Note: Merging the result": [[3, null], [35, null]], "Number of ARP entries across all devices": [[12, null], [16, "number-of-arp-entries-across-all-devices"]], "Number of Leaf Switches": [[25, "number-of-leaf-switches"], [26, "number-of-leaf-switches"]], "Number of MAC addresses across all devices": [[16, "number-of-mac-addresses-across-all-devices"], [17, null]], "Number of MACs per device per interface": [[16, "number-of-macs-per-device-per-interface"], [18, null]], "Number of VTEPs": [[25, "number-of-vteps"], [26, "number-of-vteps"]], "Number of updates": [[3, "number-of-updates"], [35, "number-of-updates"]], "Numerical value": [[3, "numerical-value"], [35, "numerical-value"]], "Operations": [[3, "operations"], [35, "operations"]], "Path": [[3, "path"], [35, "path"]], "Port Utilization": [[8, "port-utilization"], [11, null]], "Power": [[3, "power"], [35, "power"]], "Power Supply Status per Device": [[24, "power-supply-status-per-device"], [26, "power-supply-status-per-device"]], "PowerSupply Output": [[26, "powersupply-output"], [28, null]], "Queries": [[3, "queries"], [35, "queries"]], "Query parameter": [[3, "query-parameter"], [35, "query-parameter"]], "Quick overview": [[3, null], [35, "quick-overview"]], "Revision 2": [[0, "revision-2"]], "Revision 3": [[0, "revision-3"]], "Revision 4": [[0, "revision-4"]], "Revision per CloudVision release": [[0, "revision-per-cloudvision-release"]], "Square bracket operator": [[3, "square-bracket-operator"], [35, "square-bracket-operator"]], "Stats functions": [[2, "stats-functions"], [37, "stats-functions"]], "String concatenations": [[3, "string-concatenations"], [35, "string-concatenations"]], "String manipulation": [[2, "string-manipulation"], [37, "string-manipulation"]], "String value": [[3, "string-value"], [35, "string-value"]], "System Health Check": [[26, "system-health-check"], [29, null]], "System Health Examples": [[26, null]], "TAC Webinar03 2023 - BGP States": [[16, "tac-webinar03-2023-bgp-states"], [20, null]], "TCAM Capacity": [[26, "tcam-capacity"], [30, null]], "Table of Contents": [[35, "table-of-contents"], [37, "table-of-contents"]], "Temperature Sensors per Device": [[24, "temperature-sensors-per-device"], [26, "temperature-sensors-per-device"]], "Ternary expressions": [[3, "ternary-expressions"], [35, "ternary-expressions"]], "Timeseries": [[3, "id13"], [35, "id13"]], "Total VLAN Count": [[25, "total-vlan-count"], [26, "total-vlan-count"]], "Typecasts": [[3, "typecasts"], [35, "typecasts"]], "Types": [[3, "types"], [35, "types"]], "While loop": [[3, "while-loop"], [35, "while-loop"]], "Wildcards": [[3, "wcards"], [35, "wcards"]], "abs": [[2, "abs"], [37, "abs"]], "aggregate": [[2, "aggregate"], [37, "aggregate"]], "applyDeletes": [[1, "applydeletes"], [37, "applydeletes"]], "bool": [[3, "bool"], [35, "bool"]], "bottomK": [[1, "bottomk"], [37, "bottomk"]], "ceil": [[2, "ceil"], [37, "ceil"]], "complexKey": [[2, "complexkey"], [37, "complexkey"]], "deepmap": [[1, "deepmap"], [37, "deepmap"]], "deletes": [[2, "deletes"], [37, "deletes"]], "dhistogram": [[2, "dhistogram"], [37, "dhistogram"]], "dict": [[3, "dict"], [35, "dict"]], "dictHasKey": [[2, "dicthaskey"], [37, "dicthaskey"]], "dictKeys": [[2, "dictkeys"], [37, "dictkeys"]], "dictRemove": [[2, "dictremove"], [37, "dictremove"]], "dkurtosis": [[2, "dkurtosis"], [37, "dkurtosis"]], "dmean": [[2, "dmean"], [37, "dmean"]], "dmedian": [[2, "dmedian"], [37, "dmedian"]], "dpercentile": [[2, "dpercentile"], [37, "dpercentile"]], "dskew": [[2, "dskew"], [37, "dskew"]], "dstddev": [[2, "dstddev"], [37, "dstddev"]], "dsum": [[2, "dsum"], [37, "dsum"]], "dump": [[2, "dump"], [37, "dump"]], "duration": [[3, "duration"], [35, "duration"]], "dvariance": [[2, "dvariance"], [37, "dvariance"]], "equal": [[2, "equal"], [37, "equal"]], "ewlinregression": [[2, "ewlinregression"], [37, "ewlinregression"]], "exp": [[2, "exp"], [37, "exp"]], "factorial": [[2, "factorial"], [37, "factorial"]], "field": [[1, null], [37, "field"]], "fields": [[1, "fields"], [37, "fields"]], "floor": [[2, "floor"], [37, "floor"]], "formatFloat": [[2, "formatfloat"], [37, "formatfloat"]], "formatInt": [[2, "formatint"], [37, "formatint"]], "gcd": [[2, "gcd"], [37, "gcd"]], "groupby": [[2, "groupby"], [37, "groupby"]], "help": [[2, "help"], [37, "help"]], "histogram": [[2, "histogram"], [37, "histogram"]], "kurtosis": [[2, "kurtosis"], [37, "kurtosis"]], "length": [[2, "length"], [37, "length"]], "linregression": [[2, "linregression"], [37, "linregression"]], "load": [[2, "load"], [37, "load"]], "log": [[2, "log"], [37, "log"]], "log10": [[2, "log10"], [37, "log10"]], "map": [[1, "map"], [37, "map"]], "mapne": [[1, "mapne"], [37, "mapne"]], "max": [[2, "max"], [37, "max"]], "mean": [[2, "mean"], [37, "mean"]], "median": [[2, "median"], [37, "median"]], "merge": [[2, "merge"], [37, "merge"]], "min": [[2, "min"], [37, "min"]], "newDict": [[2, "newdict"], [37, "newdict"]], "now": [[2, "now"], [37, "now"]], "num": [[3, "num"], [35, "num"]], "percentile": [[2, "percentile"], [37, "percentile"]], "plot": [[2, "plot"], [37, "plot"]], "pow": [[2, "pow"], [37, "pow"]], "rate": [[2, "rate"], [37, "rate"]], "reFindAll": [[2, "refindall"], [37, "refindall"]], "reFindCaptures": [[2, "refindcaptures"], [37, "refindcaptures"]], "reMatch": [[2, "rematch"], [37, "rematch"]], "recmap": [[1, "recmap"], [37, "recmap"]], "renameFields": [[1, "renamefields"], [37, "renamefields"]], "resample": [[1, "resample"], [37, "resample"]], "round": [[2, "round"], [37, "round"]], "setFields": [[1, "setfields"], [37, "setfields"]], "skew": [[2, "skew"], [37, "skew"]], "sqrt": [[2, "sqrt"], [37, "sqrt"]], "stddev": [[2, "stddev"], [37, "stddev"]], "str": [[3, "str"], [35, "str"]], "strContains": [[2, "strcontains"], [37, "strcontains"]], "strCount": [[2, "strcount"], [37, "strcount"]], "strCut": [[2, "strcut"], [37, "strcut"]], "strHasPrefix": [[2, "strhasprefix"], [37, "strhasprefix"]], "strHasSuffix": [[2, "strhassuffix"], [37, "strhassuffix"]], "strIndex": [[2, "strindex"], [37, "strindex"]], "strReplace": [[2, "strreplace"], [37, "strreplace"]], "strSplit": [[2, "strsplit"], [37, "strsplit"]], "strToLower": [[2, "strtolower"], [37, "strtolower"]], "strToUpper": [[2, "strtoupper"], [37, "strtoupper"]], "sum": [[2, "sum"], [37, "sum"]], "time": [[3, "time"], [35, "time"]], "timeseries": [[3, "timeseries"], [35, "timeseries"]], "topK": [[1, "topk"], [37, "topk"]], "trunc": [[2, "trunc"], [37, "trunc"]], "type": [[3, "type"], [35, "type"]], "unknown": [[3, "unknown"], [35, "unknown"]], "unnestTimeseries": [[2, "unnesttimeseries"], [37, "unnesttimeseries"]], "variance": [[2, "variance"], [37, "variance"]], "where": [[1, "where"], [37, "where"]]}, "docnames": ["changelog", "doc/filters", "doc/functions", "doc/language_reference", "examples/interface_states/admin_state", "examples/interface_states/count_if_nz_out_or_in_trfk", "examples/interface_states/count_if_nz_out_trfk", "examples/interface_states/if_info_filter_port_desc", "examples/interface_states/index", "examples/interface_states/intf_counter_rate_sum_per_dev", "examples/interface_states/lanz_queue_size", "examples/interface_states/port_utilization", "examples/layer2_and_layer3/arp_entries", "examples/layer2_and_layer3/bgp_states", "examples/layer2_and_layer3/capacity_planning", "examples/layer2_and_layer3/igmp_snooping", "examples/layer2_and_layer3/index", "examples/layer2_and_layer3/mac_entries", "examples/layer2_and_layer3/macs_per_device", "examples/layer2_and_layer3/vrfs_in_vlans", "examples/layer2_and_layer3/webinar03_bgp", "examples/system_health/abootfn44", "examples/system_health/eol_planning", "examples/system_health/fn72", "examples/system_health/hardware_health_check", "examples/system_health/important_pod_count", "examples/system_health/index", "examples/system_health/ntp_stats", "examples/system_health/output_power_over_48h", "examples/system_health/system_health_check", "examples/system_health/tcam_capacity", "examples/system_health/varcore", "examples/system_health/xcvr_sn_list", "examples/system_health/xcvr_sn_list_filtered", "index", "index_doc", "index_examples", "index_stdlib"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "doc/filters.rst", "doc/functions.rst", "doc/language_reference.rst", "examples/interface_states/admin_state.rst", "examples/interface_states/count_if_nz_out_or_in_trfk.rst", "examples/interface_states/count_if_nz_out_trfk.rst", "examples/interface_states/if_info_filter_port_desc.rst", "examples/interface_states/index.rst", "examples/interface_states/intf_counter_rate_sum_per_dev.rst", "examples/interface_states/lanz_queue_size.rst", "examples/interface_states/port_utilization.rst", "examples/layer2_and_layer3/arp_entries.rst", "examples/layer2_and_layer3/bgp_states.rst", "examples/layer2_and_layer3/capacity_planning.rst", "examples/layer2_and_layer3/igmp_snooping.rst", "examples/layer2_and_layer3/index.rst", "examples/layer2_and_layer3/mac_entries.rst", "examples/layer2_and_layer3/macs_per_device.rst", "examples/layer2_and_layer3/vrfs_in_vlans.rst", "examples/layer2_and_layer3/webinar03_bgp.rst", "examples/system_health/abootfn44.rst", "examples/system_health/eol_planning.rst", "examples/system_health/fn72.rst", "examples/system_health/hardware_health_check.rst", "examples/system_health/important_pod_count.rst", "examples/system_health/index.rst", "examples/system_health/ntp_stats.rst", "examples/system_health/output_power_over_48h.rst", "examples/system_health/system_health_check.rst", "examples/system_health/tcam_capacity.rst", "examples/system_health/varcore.rst", "examples/system_health/xcvr_sn_list.rst", "examples/system_health/xcvr_sn_list_filtered.rst", "index.rst", "index_doc.rst", "index_examples.rst", "index_stdlib.rst"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"": [1, 3, 8, 11, 34, 35, 37], "0": [1, 2, 3, 5, 6, 7, 8, 10, 11, 13, 14, 15, 16, 19, 20, 21, 22, 26, 29, 30, 32, 33, 35, 36, 37], "00": [1, 2, 3, 35, 37], "0000": [2, 3, 35, 37], "000000001": [2, 37], "000000002": [2, 37], "000545": [2, 37], "01": [2, 3, 35, 37], "0100": [1, 2, 3, 35, 37], "011": [2, 37], "0123456789": [2, 37], "0123f2e4462997eb155b7c50ec148767": [1, 37], "016666666666666666": [2, 37], "01t14": [2, 37], "02": [1, 2, 3, 35, 37], "0200": [1, 2, 37], "021321282808746": [1, 37], "02t15": [3, 35], "03": [1, 2, 3, 35, 37], "035469519006775": [1, 37], "04": [1, 2, 3, 35, 37], "0485322987015024e": [2, 37], "05": [1, 2, 3, 35, 37], "05024018915018": [1, 37], "06": [1, 2, 37], "06172043150084": [2, 37], "06273653863866613": [2, 37], "07": [1, 2, 3, 35, 37], "0700": [3, 35], "08": [1, 2, 37], "0845523145094376e": [2, 37], "09": [1, 2, 37], "0x1": [2, 37], "1": [0, 1, 2, 3, 7, 8, 9, 11, 13, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 29, 35, 36, 37], "10": [1, 2, 3, 8, 10, 35, 37], "100": [2, 8, 11, 26, 30, 37], "1000000000": [16, 20], "10000018299125094": [3, 35], "10000027274982": [3, 35], "100000432767384": [3, 35], "100001": [2, 37], "1046901332212": [1, 37], "10k1": [3, 35], "10m": [3, 35], "10z": [2, 37], "11": [1, 2, 3, 35, 37], "110": [2, 37], "12": [1, 2, 3, 35, 37], "123": [1, 2, 37], "125000": [8, 9], "126216167169341": [1, 37], "12a": [3, 35], "13": [1, 2, 3, 35, 37], "139184309225689": [1, 37], "14": [1, 2, 3, 35, 37], "15": [2, 3, 35, 37], "15682": [2, 37], "15e": [3, 35], "15m": [2, 3, 6, 8, 26, 28, 35, 37], "15m0": [3, 35], "16": [1, 2, 3, 35, 37], "16722874112898": [2, 37], "167535": [3, 35], "168h0m0": [3, 35], "169": [1, 37], "17": [1, 2, 3, 35, 37], "176146444123674e": [2, 37], "179871": [2, 37], "18": [1, 2, 37], "180": [3, 35], "1800167411644353": [1, 37], "184087816704434": [2, 37], "184361": [3, 35], "18519049983288": [2, 37], "19": [1, 3, 35, 37], "192": 36, "1a": 36, "1c": 36, "1d": [2, 37], "1e": [3, 35], "1h": [2, 37], "1m": [2, 3, 8, 9, 10, 35, 37], "2": [1, 2, 3, 6, 7, 8, 11, 13, 15, 20, 21, 24, 25, 26, 34, 35, 36, 37], "20": [1, 3, 35, 37], "200": [1, 2, 37], "20000036598250187": [3, 35], "2006": [3, 35], "2019": [1, 2, 37], "2020": 0, "2021": [0, 1, 2, 3, 35, 37], "2022": [0, 1, 2, 3, 35, 37], "2023": [0, 36], "2041": [23, 26], "204460235": [1, 37], "205235103": [1, 37], "20k2": [3, 35], "21": [1, 2, 37], "2121957107961934e": [2, 37], "2131": [23, 26], "2134": [23, 26], "216": [2, 37], "217673": [2, 37], "22": [1, 2, 37], "23": [1, 2, 3, 35, 37], "230": [2, 37], "2338853823764566e": [2, 37], "234567": [2, 37], "24": [2, 37], "24h": [3, 8, 10, 35], "25": [2, 3, 35, 37], "252194027605128e": [2, 37], "2568db4a33177968a78c4fd5a8232159": [1, 37], "26": [1, 2, 3, 35, 37], "261383897237806": [2, 37], "267740573382639e": [2, 37], "269740014": [1, 37], "26t14": [3, 35], "26t16": [3, 35], "27": [2, 3, 35, 37], "273451668": [1, 37], "28": [2, 37], "29": [1, 2, 3, 35, 37], "2909": [2, 37], "2h45m": [3, 35], "2m": [1, 2, 37], "3": [1, 2, 3, 7, 8, 13, 20, 21, 23, 25, 26, 34, 35, 36, 37], "30": [1, 2, 3, 35, 37], "300m": [3, 35], "302585092994046": [2, 37], "30k3": [3, 35], "31": [1, 2, 3, 35, 37], "313228804713885": [1, 37], "32": [1, 2, 3, 35, 37], "33": [1, 2, 3, 35, 37], "33333333": [2, 37], "333333333334": [2, 37], "3339": [3, 35], "34": [2, 3, 35, 37], "34201204121343765": [2, 37], "347884243": [1, 37], "35": [3, 35], "359334702641604e": [2, 37], "36": [3, 35], "37": [3, 35], "37487749955894": [2, 37], "38": [3, 35], "39436215194667e": [2, 37], "3m": [3, 35], "3m0": [3, 35], "4": [1, 2, 3, 8, 9, 13, 16, 20, 21, 26, 35, 37], "413745251460854": [1, 37], "42": [1, 3, 35, 37], "422506200403397": [2, 37], "43": [1, 37], "4375471112800824e": [2, 37], "44": [2, 37], "45": [1, 37], "4509288322558405e": [2, 37], "46": [1, 3, 35, 37], "46781924765547": [2, 37], "47": [3, 35], "47126937459381": [2, 37], "478329283748833": [3, 35], "48": [1, 37], "48504158758581": [2, 37], "485041588": [2, 37], "48h": [26, 28], "48yc8": [23, 26], "49": [2, 37], "4h": [16, 20], "5": [1, 2, 3, 35, 37], "50": [2, 37], "50000037628384": [3, 35], "5000009149562546": [3, 35], "5000081856910986": [3, 35], "50007045163161": [3, 35], "50011253961932": [3, 35], "50100684000513": [3, 35], "501376131906685": [2, 37], "520567819910998": [2, 37], "52073192182222": [2, 37], "526568822982724e": [2, 37], "54": [1, 2, 37], "5425229615148055e": [2, 37], "55": [2, 37], "56": [2, 3, 35, 37], "562088032011452": [1, 37], "562415784963335": [2, 37], "5682872900e": [2, 37], "57": [2, 37], "57132998707": [2, 37], "58": [1, 2, 37], "581944406432633": [2, 37], "58473123455124": [2, 37], "59": [2, 3, 35, 37], "597529745280074": [2, 37], "5h": [3, 35], "5h0m0": [3, 35], "5h15": [3, 35], "5h30m": [3, 35], "5m": [2, 3, 35, 37], "6": [1, 2, 3, 16, 20, 21, 26, 35, 37], "60": [3, 26, 29, 35], "60470951346135": [2, 37], "60952274150811": [2, 37], "61": [3, 35], "615865": [3, 35], "6323da7d2b542b5d09630f87351bea41": [1, 37], "638619033792": [3, 35], "641208840183708e": [2, 37], "64152704258496": [3, 35], "64315689104305": [2, 37], "647003241959368": [2, 37], "64771549594622": [2, 37], "652391153117858": [1, 37], "65325390983907": [2, 37], "653320559746086e": [2, 37], "6547381850231": [1, 37], "65664047328105": [2, 37], "6666666666666666": [2, 37], "67": [3, 35], "674314": [3, 35], "68": [1, 37], "68106989897461": [3, 35], "6923313578244437": [2, 37], "7": [1, 2, 3, 8, 10, 16, 20, 21, 23, 26, 35, 37], "70": [26, 29], "7010378175218949": [1, 37], "71": [1, 37], "716099165": [1, 37], "720": [3, 35], "722908": [1, 37], "724350983923758": [1, 37], "7280sr3": [23, 26], "7280sr3k": [23, 26], "739134103556832": [2, 37], "7431002727844832": [2, 37], "75": [2, 37], "753872494": [1, 37], "756429823529587": [2, 37], "76746009308496": [3, 35], "772750473": [1, 37], "77301344308594": [3, 35], "78": [1, 37], "78515625": [3, 35], "794969": [2, 37], "8": [1, 2, 3, 16, 20, 35, 37], "80": [26, 29], "80002342288012": [3, 35], "801633107494212e": [2, 37], "816078774499992": [1, 37], "828728378483": [3, 35], "838672949594982e": [2, 37], "84247911778802": [2, 37], "8444249301319680p": [2, 37], "844870569797877e": [2, 37], "845057565692196": [1, 37], "84759166533158": [2, 37], "86225375105": [2, 37], "864572656164906": [1, 37], "868898231943206": [1, 37], "870252166": [3, 35], "870256382": [3, 35], "870363498": [3, 35], "872056741171672": [2, 37], "8729": [2, 37], "8729000000": [2, 37], "889330661612725": [2, 37], "899238295809337": [1, 37], "9": [1, 2, 3, 21, 26, 35, 37], "90": [2, 37], "923904": [2, 37], "94748623552414": [2, 37], "9503": [2, 37], "97": [2, 37], "971200542852744e": [2, 37], "981215360110563e": [2, 37], "A": [2, 3, 8, 11, 35, 37], "AND": [3, 35], "AS": [13, 16, 20], "And": [13, 16, 20], "As": [3, 35], "By": [3, 35], "For": [2, 36, 37], "If": [1, 2, 13, 16, 26, 30, 34, 37], "In": [2, 3, 35, 37], "It": [1, 2, 3, 35, 37], "Its": [1, 3, 35, 37], "NO": [3, 35], "NOT": [3, 35], "No": 0, "OR": [3, 35, 36], "On": [2, 36, 37], "TO": [3, 35], "The": [1, 2, 3, 34, 35, 37], "There": [1, 3, 35, 37], "These": [3, 35], "To": [3, 8, 11, 35], "With": [1, 3, 35, 37], "_": [1, 2, 3, 35, 37], "_1": [1, 37], "_2": [1, 37], "_3": [1, 37], "_4": [1, 37], "_bgpdevic": [16, 20], "_bracketindex": [3, 35], "_brackettim": [3, 35], "_count": [12, 14, 16, 17], "_d": [3, 35], "_devic": [3, 4, 8, 9, 15, 16, 18, 26, 28, 35], "_endtim": [3, 35], "_i": [3, 35], "_index": [1, 37], "_interfac": [8, 9], "_kei": [1, 3, 8, 9, 11, 13, 14, 16, 20, 24, 25, 26, 29, 30, 35, 37], "_metavar": [3, 35], "_neighborip": [16, 20], "_pod_nam": [13, 14, 16, 20, 24, 25, 26, 29, 30], "_portdescript": [7, 8], "_regexinput": [26, 33], "_src": [1, 37], "_starttim": [3, 35], "_switch_rol": [8, 11, 24, 26, 29], "_time": [1, 37], "_timewindowend": [8, 9, 10], "_timewindowstart": [8, 9, 10], "_updindex": [1, 37], "_updtim": [1, 37], "_valu": [1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20, 21, 24, 25, 26, 27, 29, 30, 31, 32, 33, 35, 37], "_vrf": [16, 19], "aa": [2, 37], "ab": [3, 35], "abcd": [2, 37], "aboot": [21, 26], "about": [3, 35], "abov": [3, 35], "absolut": [2, 37], "ac": [3, 35], "accept": 36, "access": [2, 3, 7, 8, 35, 37], "access_token": 36, "accessvlan": [7, 8], "accord": [2, 37], "accordingli": [2, 37], "accross": [2, 37], "across": [2, 3, 35, 36, 37], "activ": [5, 6, 8], "actual": [8, 10], "ad": [1, 2, 3, 15, 16, 35, 36, 37], "adapt": [3, 35], "add": [3, 8, 9, 13, 15, 16, 20, 35], "address": [13, 20, 36], "admin": 36, "adminenabledstateloc": [4, 8], "advanc": 34, "affect": 36, "afi": [16, 20], "afi_nam": [16, 20], "after": [1, 2, 3, 35, 37], "agent": [26, 31], "aggdata": [8, 10], "aggreg": [0, 3, 6, 8, 9, 10, 26, 28, 35], "ago": [2, 37], "alertrais": [24, 26], "all": [1, 2, 3, 5, 6, 8, 11, 19, 20, 30, 33, 35, 36, 37], "allow": [3, 35], "along": [3, 35], "alreadi": [1, 37], "also": [1, 3, 35, 37], "alter": [1, 3, 35, 37], "alwai": [2, 3, 35, 37], "am": [2, 37], "amount": [3, 35], "an": [1, 2, 3, 4, 8, 35, 37], "analyt": [1, 2, 3, 5, 6, 8, 9, 10, 11, 13, 14, 16, 20, 22, 23, 24, 25, 26, 28, 29, 30, 31, 35, 37], "ani": [1, 2, 3, 26, 31, 35, 37], "anoth": [3, 8, 11, 35], "answer": [3, 35], "anyth": [3, 35], "api": [3, 15, 16, 35, 36], "apnortheast": 36, "app": [3, 35], "app1": 36, "app_nam": 36, "appli": [1, 2, 3, 5, 6, 8, 35, 37], "applic": [1, 36, 37], "applydelet": 0, "appropri": [1, 3, 35, 37], "aql": [0, 2, 3, 8, 9, 35], "ar": [1, 2, 3, 15, 19, 26, 30, 35, 36, 37], "archer": [3, 24, 26, 32, 33, 35], "argument": [1, 2, 3, 35, 37], "argument1": [3, 35], "argument2": [3, 35], "arista": [34, 36], "arp": [14, 36], "arpcount": [14, 16], "arpentri": [12, 14, 16], "ascii": [3, 35], "assert": [3, 35], "assign": [3, 35], "associ": [2, 3, 35, 37], "attach": [3, 35], "attempt": [2, 37], "ausoutheast": 36, "australia": 36, "averag": [2, 3, 35, 37], "avg": [2, 3, 6, 8, 9, 10, 26, 28, 35, 37], "avgdata": [3, 35], "az": [2, 37], "b": [2, 3, 35, 36, 37], "b6070": [26, 29], "b6080": [26, 29], "backslash": [3, 35], "bad032986065e8dc14cbb6472ec314a6": [1, 37], "bar": [3, 35], "base": [1, 2, 37], "basic": 34, "bb": [2, 37], "bc": [2, 37], "becaus": [1, 3, 35, 37], "been": [3, 35], "befor": [1, 3, 35, 37], "begin": [3, 35], "behav": [3, 35], "behaviour": [2, 37], "being": [1, 3, 35, 37], "below": [3, 35], "between": [1, 2, 3, 35, 37], "bfree": [26, 31], "bgp": 36, "bgpneighbor": [13, 16, 20], "bgppeera": [13, 16, 20], "bgppeerafisafiact": [16, 20], "bgppeerafisafiactivenam": [16, 20], "bgppeerafisafistat": [16, 20], "bgppeerinfostatu": [16, 20], "bgppeerinfostatusentri": [13, 16, 20], "bgppeerintooroutofestablishedtim": [16, 20], "bgppeerlocaladdr": [13, 16, 20], "bgppeerstatisticsentri": [16, 20], "bgpstate": [13, 16, 20], "big": [3, 35], "binari": [2, 37], "block": [3, 26, 31, 35], "bool": [2, 37], "boolean": [1, 2, 37], "both": [2, 3, 35, 37], "bottom": [3, 35], "bottomk": 0, "bracket": 34, "branch": [1, 37], "bridg": [7, 8, 14, 15, 16, 17, 18, 25, 26, 36], "bugalert": [1, 22, 26, 37], "build": [16, 19, 26, 30], "built": [3, 35], "c": [3, 35, 36], "call": [1, 2, 3, 35, 37], "can": [1, 2, 3, 8, 10, 15, 16, 34, 35, 37], "canada": 36, "cannot": [1, 2, 3, 35, 37], "capac": 36, "captur": [2, 37], "card": [21, 26], "cardslot": [21, 26], "case": [1, 2, 3, 35, 37], "cast": [3, 35], "cat": 36, "catch": [3, 35], "cd0eadbeea126915ea78e0fb4dc776ca": [1, 37], "cell": [4, 8, 16, 20, 24, 26], "central1": 36, "certain": [3, 35], "cest": [1, 2, 37], "cet": [1, 37], "chain": [3, 35], "chang": [3, 34, 35], "channel512": [1, 37], "channel532": [1, 37], "charact": [3, 35], "chassi": [7, 8, 21, 26], "chassisid": [7, 8], "chassisidentifi": [7, 8], "check": [2, 3, 35, 36, 37], "chip": [26, 30], "choos": [8, 11], "clearer": [1, 37], "cli": [3, 35], "closest": [2, 37], "cloudvis": [1, 3, 34, 35, 37], "code": [3, 35], "coercion": [2, 37], "collect": [1, 3, 35, 37], "collis": [2, 37], "colon": [3, 35], "column": [13, 16, 20], "combin": [3, 35], "comma": [3, 15, 16, 35], "comment": [3, 35], "common": [2, 3, 35, 37], "compar": [1, 3, 35, 37], "comparison": 34, "complex": [2, 37], "complexkei": [3, 13, 14, 16, 20, 24, 25, 26, 29, 30, 35], "compos": [3, 35], "comput": [1, 2, 3, 5, 6, 8, 35, 37], "condit": [3, 35], "config": [3, 4, 7, 8, 11, 14, 16, 19, 25, 26, 35], "configur": [8, 11, 34, 36], "confus": [3, 35], "congest": [8, 10], "constant": [1, 2, 37], "contain": [1, 2, 3, 8, 9, 35, 37], "content": 3, "context": [3, 35], "control": [3, 35], "convert": [2, 37], "cool": [24, 26], "copi": [1, 2, 37], "core": 36, "correct": 36, "correspond": [1, 2, 3, 35, 37], "count": [2, 13, 14, 16, 20, 36, 37], "counter": 36, "cover": [3, 35], "cpod1": [25, 26], "crash": [26, 31], "creat": [1, 3, 15, 16, 35, 37], "cross": [2, 37], "curl": 36, "curli": [3, 35], "current": [1, 2, 3, 22, 26, 35, 37], "custom": 34, "cv": 36, "cvaa": [0, 36], "cvp": 0, "cx_tmp": [21, 26], "d": [1, 2, 3, 15, 16, 21, 26, 35, 36, 37], "d1": [3, 35], "d2": [3, 35], "dashboard": [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, 33, 34, 35], "data": [1, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 26, 28, 29, 31, 33, 34, 35], "data1": [16, 19], "databas": [3, 35], "datadeviceanalyt": [8, 10], "datasetinfo": [22, 23, 26], "datasetnam": [3, 35], "datasettyp": [3, 35], "dc": [22, 26], "dd": [2, 37], "ddd": [2, 37], "deal": [3, 35], "decai": [2, 37], "decim": [2, 3, 35, 37], "declar": [3, 35], "decommiss": [3, 35], "default": [2, 8, 11, 15, 20, 36, 37], "defin": [1, 3, 35, 37], "delet": 1, "deleteal": [2, 37], "delimit": [3, 35], "densiti": [26, 30], "depend": [2, 3, 35, 37], "deprecatedreleasenum": [22, 26], "depth": [1, 37], "describ": [2, 3, 35, 37], "descript": [11, 36], "design": [3, 35], "desir": [2, 37], "detail": [3, 35], "determin": [3, 35], "dev": [8, 11], "devdata": [26, 27], "deviat": [2, 37], "devic": [2, 3, 5, 6, 10, 11, 14, 19, 20, 22, 25, 28, 29, 30, 35, 36, 37], "deviceandinterfacenam": [3, 35], "deviceavg": [2, 37], "devicedata": [8, 10, 22, 26, 29], "devicedisk": [26, 29], "devicediskstat": [26, 31], "devicehostnam": [8, 10], "deviceid": [13, 14, 16, 20, 24, 25, 26, 29, 30], "deviceidstr": [8, 11], "devicekei": [7, 8, 11, 14, 16, 18, 19, 21, 24, 25, 26, 30], "devicenam": [3, 35], "devicescpod1": [25, 26], "devicesess": [13, 16, 20], "devicesincpod1": [25, 26], "devicesn": [8, 10, 22, 26], "devicesswitchlabel": [14, 16, 24, 26, 29], "deviceupd": [22, 26], "devicev": [7, 8], "devicevalu": [8, 11, 14, 16, 18, 19, 20, 21, 24, 25, 26, 30], "devid": [8, 11, 26, 27], "dict": [1, 8, 10, 13, 16], "dicthaskei": [5, 6, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 20, 21, 22, 24, 25, 26, 28, 29, 30, 31], "dictionari": [8, 9, 16, 19], "dictkei": [7, 8, 15, 16], "did": [1, 37], "differ": [1, 2, 3, 35, 37], "digit": [2, 3, 35, 37], "direct": 34, "directivenam": [3, 35], "directli": [2, 3, 35, 37], "disk": [26, 29], "diskstat": [26, 31], "displai": [3, 35], "dist": [2, 37], "distribut": [2, 37], "divid": [8, 9], "divisor": [2, 37], "dkei": [23, 26], "do": [3, 35], "document": [3, 35], "doe": [1, 2, 3, 35, 37], "doesn": [2, 37], "don": [1, 3, 35, 37], "doubl": [3, 35], "down": [16, 20], "download": [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, 33], "drop": [8, 10], "dropdown": [8, 11], "durat": [1, 16, 20, 37], "dure": [3, 35], "dval": [23, 26], "e": [2, 3, 35, 36, 37], "e00p": [2, 37], "each": [1, 2, 3, 5, 6, 8, 13, 16, 20, 35, 37], "easili": [3, 35], "ee": [2, 37], "effici": [3, 35], "egw": [14, 16, 26, 30], "either": [1, 2, 3, 35, 37], "element": [1, 2, 8, 11, 13, 14, 16, 20, 24, 25, 26, 29, 30, 37], "els": [8, 11, 13, 14, 15, 16, 18, 20, 21, 24, 25, 26, 29, 30, 34], "empti": [1, 2, 3, 8, 10, 35, 37], "enabl": [4, 8], "end": [1, 2, 3, 35, 37], "endofhardwarermarequest": [22, 26], "endoflif": [22, 26], "endofrmarequest": [22, 26], "endofsal": [22, 26], "endofsupport": [22, 26], "endoftacsupport": [22, 26], "entir": [3, 5, 6, 8, 35], "entmib": [21, 26], "entri": [1, 2, 3, 5, 6, 8, 26, 30, 35, 36, 37], "environ": [3, 24, 26, 28, 35], "eo": [26, 31, 34], "eol": 36, "eosvers": [22, 26], "equal": [3, 35], "equival": [3, 35], "error": [1, 3, 35, 37], "establish": 20, "etc": [3, 7, 8, 34, 35], "eth": [4, 7, 8, 11], "ethernet": [8, 10, 11], "ethernet1": [2, 3, 35, 37], "ethernet2": [2, 3, 35, 37], "ethernet49": [1, 37], "ethernet5": [3, 35], "ethernet50": [2, 3, 35, 37], "ethernet51": [1, 2, 37], "ethernet6": [3, 35], "ethernet8": [1, 37], "ethgroup": [15, 16], "euwest": 36, "evalu": [1, 37], "even": [1, 3, 35, 37], "event": 34, "ever": [1, 37], "everi": [1, 3, 13, 16, 20, 35, 37], "evpn": [20, 26, 30], "ewlinregress": 0, "exact": [3, 35], "exactli": [2, 37], "exampl": [3, 15, 34, 35], "except": [2, 37], "exclud": [2, 37], "exclus": [1, 37], "execut": 34, "exist": [1, 3, 35, 36, 37], "explicitli": [3, 35], "expon": [2, 37], "exponenti": [2, 37], "export": [16, 20], "express": [1, 2, 34, 37], "extract": [3, 35], "f": [2, 37], "fact6": [3, 35], "factori": [3, 35], "fail": [1, 37], "fals": [2, 3, 8, 10, 21, 24, 26, 35, 37], "fanshwstatu": [24, 26], "fanskei": [24, 26], "fansvalu": [24, 26], "featur": [3, 26, 30, 35], "fec": [26, 30], "fetch": [3, 35], "ff": [2, 37], "field": [0, 2, 3, 5, 6, 8, 9, 11, 14, 16, 20, 26, 28, 29, 30, 32, 33, 35], "file": [2, 3, 35, 36, 37], "filter": [0, 1, 2, 9, 34, 36], "filter1": [3, 35], "filter2": [3, 35], "filter3": [3, 35], "filteredhwcapl2": [26, 30], "filteredhwcapl3": [26, 30], "filteredhwcapmcast": [26, 30], "filteredigmpsnoop": [15, 16], "filteredpsustat": [24, 26], "filteredsku": [22, 26], "filteredtemperaturestat": [24, 26], "filteredxcvrstat": [26, 32], "filternam": [3, 35], "firmwarerev": [21, 26], "first": [1, 2, 3, 35, 37], "fit": [2, 37], "fix": [21, 26], "fixedsystem": [21, 26], "flatten": [2, 37], "float": [2, 3, 35, 37], "float64": [2, 3, 35, 37], "fn44": 36, "fn72": 36, "follow": [2, 3, 34, 35, 37], "foo": [2, 3, 35, 37], "foobarbaztootartaz": [2, 37], "forbidden": [3, 35], "format": [2, 3, 35, 37], "formatfloat": 0, "formatint": 0, "former": [2, 37], "forward": [3, 15, 16, 35], "found": [15, 16], "fraction": [2, 37], "free": [8, 11], "frequenc": [2, 37], "freshli": [1, 37], "from": [1, 2, 3, 8, 11, 35, 37], "ft": [2, 37], "full": [2, 3, 35, 37], "fulli": [3, 35], "function": [0, 1, 8, 9, 34], "functionnam": [3, 35], "fx_temp": [21, 26], "g": [3, 35, 36], "gatewai": [26, 30], "gener": [3, 35], "germani": 36, "get": [3, 5, 6, 7, 8, 9, 10, 11, 16, 19, 26, 30, 35], "given": [1, 2, 37], "global": [3, 35], "gmt": [2, 3, 35, 37], "gnmienabl": [1, 37], "go": [3, 35], "got": [3, 35], "govendorinfo": [26, 32, 33], "graph": [8, 9], "greater": [2, 3, 35, 37], "greatest": [2, 37], "group": [2, 37], "h": [3, 35], "ha": [2, 3, 35, 37], "had": [3, 35], "handl": [3, 35], "happen": [3, 35], "hardwar": [2, 3, 21, 22, 29, 30, 32, 33, 34, 35, 36, 37], "hardwareagg": [3, 35], "have": [1, 2, 3, 13, 16, 30, 34, 35, 36, 37], "hdl": [8, 11, 26, 30], "header": 36, "health": [34, 36], "hello": [3, 35], "here": [1, 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, 33, 35, 37], "hexadecim": [2, 37], "high": [26, 30], "highest": [1, 3, 35, 37], "histor": [1, 37], "hold": [3, 35], "horizon": [8, 9], "host": [26, 30], "hostnam": [7, 8, 10, 22, 23, 26, 31], "hour": [3, 35], "how": [2, 3, 35, 37], "howev": [3, 35], "hsh14075043": [1, 37], "hsh14075051": [1, 37], "hsh14280171": [1, 2, 37], "hsh14420467": [1, 37], "http": 36, "hw": [22, 26], "hwcapl2": [26, 30], "hwcapl3": [26, 30], "hwcapmcast": [26, 30], "hweol": [22, 26], "hwstatu": [24, 26], "i": [1, 2, 3, 4, 5, 6, 7, 8, 13, 15, 16, 20, 26, 30, 34, 35, 37], "i60": [26, 29], "id": [13, 16, 19, 20, 23, 26], "ident": [2, 3, 35, 37], "identifi": [7, 8], "igmp": 36, "igmpsnoop": [15, 16, 36], "ignor": [1, 2, 37], "illeg": [3, 35], "imag": [2, 34, 37], "import": 36, "inbound": 36, "includ": [3, 15, 16, 35, 36], "include_path": 36, "includedecommissioneddevic": [3, 35], "independ": [3, 35], "index": [1, 2, 3, 35, 37], "info": [7, 8], "inform": [1, 3, 35, 36, 37], "ing": [2, 37], "initi": [2, 37], "inmulticastpkt": [3, 35], "inner": [5, 6, 8], "inoctet": [3, 5, 8, 9, 35], "input": [1, 2, 7, 8, 11, 36, 37], "inputvar": [3, 35], "insert": [3, 35], "instead": [1, 3, 35, 37], "int": [2, 3, 35, 37], "integ": [2, 3, 35, 37], "interact": [3, 35], "intercept": [2, 37], "interfac": [2, 3, 10, 11, 15, 19, 34, 35, 36, 37], "interfacedata": [3, 8, 10, 35], "interfacekei": [7, 8, 16, 19], "interfacelist": [15, 16], "interfacev": [7, 8], "interfacevalu": [16, 19], "intern": [2, 37], "interpret": [2, 3, 35, 37], "interv": [1, 2, 3, 8, 10, 35, 37], "intervaltomonitor": [8, 10], "intf": [3, 15, 16, 18, 19, 35], "intfbool": [15, 16], "intfconfig": [4, 7, 8, 11, 16, 19], "intfkei": [7, 8], "intfratesinfilt": [8, 9], "intfratesoutfilt": [8, 9], "intfstat": [15, 16], "intfstatu": [7, 8], "intfval": [7, 8], "inucastpkt": [3, 35], "inv": [22, 26], "inventori": [22, 23, 26], "io": 36, "ip": [13, 16, 20], "ipv4": [16, 20], "ipv4unicast": [16, 20], "ist": [2, 3, 35, 37], "item": [2, 37], "iter": [3, 35], "ith": [2, 37], "its": [1, 2, 3, 35, 37], "itself": [1, 3, 35, 37], "j": [3, 35], "ja": [23, 26], "japan": 36, "jas12200014": [1, 37], "jas14170008": [1, 37], "jas14210057": [1, 37], "jas16040045": [1, 37], "jas17070003": [1, 2, 37], "jas17250006": [1, 37], "jas17250010": [1, 37], "jas17510146": [1, 37], "jas18170075": [1, 37], "jericho": [26, 30], "jericho0": [26, 30], "johndo": [3, 35], "jpe": [23, 26], "jpe12345": [3, 35], "jpe123456": [2, 3, 35, 37], "jpe14120478": [1, 37], "jpe14171444": [1, 37], "jpe14250224": [1, 37], "jpe14383408": [1, 37], "jpe17191574": [1, 2, 3, 35, 37], "jpe19280519": [1, 37], "jpe20244151": [1, 37], "jpe654321": [2, 3, 35, 37], "json": [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, 33, 35, 36, 37], "just": [1, 3, 35, 37], "k": [1, 3, 35, 37], "k1": [1, 3, 35, 37], "k10": [1, 37], "k11": [1, 37], "k2": [1, 3, 35, 37], "k3": [1, 3, 35, 37], "k3l": [1, 37], "k4": [1, 37], "k4l": [1, 37], "k5": [1, 37], "kafi": [16, 20], "keep": [1, 5, 6, 8, 37], "kei": [1, 2, 3, 9, 13, 16, 20, 22, 26, 28, 35, 36, 37], "kept": [1, 2, 37], "kernel": [26, 31], "key0": [1, 37], "key01": [1, 37], "key02": [1, 37], "key1": [1, 2, 3, 35, 37], "key1l": [1, 37], "key2": [1, 2, 3, 35, 37], "key2l": [1, 37], "key3": [1, 2, 3, 35, 37], "key4": [2, 3, 35, 37], "key5": [3, 35], "keyb": [3, 35], "keyword": 34, "kind": [3, 35], "kingdom": 36, "know": [3, 35], "kval": [16, 20], "kx": 36, "l": [1, 37], "l2": [7, 8, 26, 30], "l2discoveri": [7, 8], "l2vpn": [16, 20], "l2vpnevpn": [16, 20], "l3": [16, 19, 26, 30], "label": [8, 11, 13, 20, 24, 25, 26, 29, 30], "languag": 34, "lanz": 36, "last": [1, 3, 22, 26, 35, 37], "latenc": [8, 10], "latest": [1, 2, 3, 5, 6, 8, 35, 37], "latestr": [5, 8], "latestratesbyinterfac": [6, 8], "layer": [1, 34, 36, 37], "lceil": [2, 37], "ldl": [26, 30], "leaf": [1, 30, 37], "lem": [26, 30], "length": [3, 5, 6, 8, 14, 15, 16, 20, 21, 25, 26, 33, 35], "let": [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, 33, 35, 37], "letter": [2, 3, 35, 37], "level": [1, 2, 3, 35, 37], "lfloor": [2, 37], "librari": [0, 3, 34, 35], "lifecycl": [22, 26], "like": [1, 2, 3, 35, 37], "limit": [3, 35], "line": [2, 3, 16, 20, 35, 37], "linear": [2, 37], "linecard0": [26, 30], "link": [3, 35], "linregress": 0, "list": [1, 2, 15, 36, 37], "liter": [2, 3, 35, 37], "lldp": [7, 8], "lldpkei": [7, 8], "lldppeer": [7, 8], "lldpval": [7, 8], "load": [8, 11], "local": [7, 8], "log": [16, 20, 34], "log_": [2, 37], "logentri": [16, 20], "logic": [3, 35], "long": [1, 2, 3, 35, 37], "loop": [13, 16, 20, 34], "low": [26, 30], "lower": [2, 3, 35, 37], "lowercas": [2, 37], "lowest": [1, 3, 35, 37], "lpm": [26, 30], "lvert": [2, 37], "m": [3, 35], "mac": [14, 26, 30, 36], "macaddr": [15, 16], "macaddrintf": [15, 16], "macconfigcount": [14, 16], "macstatuscount": [14, 16], "made": [3, 35], "mai": 34, "main": [1, 3, 35, 37], "make": [3, 35, 36], "manag": [3, 35], "management1": [1, 37], "manipul": [3, 35], "manual": 34, "map": [2, 5, 6, 8, 9, 10, 11, 12, 14, 15, 16, 17, 21, 25, 26, 27, 29, 30, 31, 32, 33], "mapn": [26, 32, 33], "match": [1, 2, 3, 35, 36, 37], "maxlimit": [26, 30], "mbp": [8, 9], "mcast": [26, 30], "mcdb": [26, 30], "mean": [1, 3, 35], "member": [15, 16], "meminfo": [26, 29], "memoryusag": [26, 29], "merg": [1, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 30, 31], "mergeddevicedata": [8, 10], "metavari": [1, 3, 35, 37], "method": [2, 37], "microsecond": [3, 35], "millisecond": [3, 35], "min": [3, 35], "minut": [3, 8, 9, 35], "miss": [1, 37], "mode": [3, 7, 8, 35], "model": [23, 26], "modelnam": [22, 23, 26], "modifi": [3, 35], "moment": [1, 37], "monitor": [8, 10], "more": [1, 2, 3, 35, 37], "most": [1, 2, 3, 35, 37], "mostli": [1, 3, 35, 37], "msap": [7, 8], "much": [1, 3, 35, 37], "multicast": [26, 30], "multipl": [2, 37], "must": [1, 2, 3, 35, 37], "my": [3, 35], "mybooleanvar": [3, 35], "mydataset": [3, 35], "mydict": [3, 35], "mykei": [3, 35], "myplotimg": [2, 37], "mystr": [3, 35], "mytimeseri": [3, 35], "mytimeseriesordict": [2, 37], "myvalu": [3, 35], "myvar": [2, 37], "myvar1": [3, 35], "myvar_name2": [3, 35], "n": [1, 2, 3, 8, 11, 35, 37], "n2": [3, 35], "na": 36, "name": [0, 2, 4, 7, 8, 13, 16, 20, 24, 26, 34, 37], "name1": [2, 37], "name2": [2, 37], "nanosecond": [3, 35], "nativ": [3, 35], "natur": [2, 37], "nbarp": [14, 16], "nbport": [8, 11], "nbr": [14, 16], "nbunusedport": [8, 11], "nbvlan": [14, 16], "nbvrf": [14, 16], "nd": [14, 16], "need": [3, 15, 16, 35, 36], "neg": [2, 3, 35, 37], "neighbor": [13, 16, 20], "neighborentri": [14, 16], "neighbour": [7, 8], "nest": [1, 2, 3, 35, 37], "network": [5, 6, 8], "never": [4, 8], "new": [1, 2, 3, 16, 19, 26, 30, 35, 37], "newdict": [1, 3, 4, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 29, 31, 35], "newkey0": [1, 37], "newkey01": [1, 37], "newkey2": [1, 37], "nexthop": [26, 30], "nil": [1, 2, 3, 35, 37], "node": [1, 37], "non": [2, 3, 35, 36, 37], "none": [16, 20, 26, 30], "norcal": [21, 26], "northeast1": 36, "notat": [3, 35], "note": [8, 9], "now": [3, 16, 20, 35], "nth": [2, 37], "ntp": 36, "ntpdata": [26, 27], "null": 36, "num": [1, 2, 16, 20, 21, 22, 23, 26, 37], "number": [1, 2, 5, 6, 8, 10, 11, 23, 36, 37], "numbermac": [16, 18], "numbervlan": [25, 26], "numer": [2, 37], "numfield": [1, 2, 37], "object": [2, 8, 9, 37], "obtain": [2, 37], "occur": [2, 37], "occurr": [2, 37], "offer": [3, 35], "offici": 34, "offset": [26, 27], "ok": [24, 26], "old": [1, 37], "older": [2, 3, 35, 37], "oldest": [3, 35], "onc": [3, 35], "one": [1, 2, 3, 13, 16, 20, 35, 37], "onli": [1, 3, 5, 6, 8, 9, 15, 16, 19, 20, 35], "oo": [2, 37], "oper": [2, 34, 37], "operstatu": [7, 8], "option": [1, 2, 3, 35, 37], "order": [3, 35], "origin": [1, 37], "other": [1, 2, 3, 35, 37], "out": [1, 8, 9, 26, 28, 37], "outbound": 36, "outmulticastpkt": [3, 35], "outoctet": [3, 5, 6, 8, 9, 35], "output": [1, 2, 3, 27, 35, 36, 37], "outsid": [3, 35], "over": [3, 13, 16, 20, 35], "overview": 34, "page": [3, 34, 35], "pair": [1, 2, 3, 35, 37], "panic": [3, 35], "paramet": [1, 2, 37], "parenthes": [3, 35], "pars": [2, 37], "part": [2, 3, 26, 30, 35, 37], "pass": [1, 3, 35, 37], "past": [2, 37], "path": [1, 2, 34, 37], "pathelement1": [1, 37], "pathelement2": [1, 37], "pathelement3": [1, 37], "pathelement4": [1, 37], "pe1": [1, 37], "pe2": [1, 37], "pe3": [1, 37], "peer": [13, 16, 20, 26, 27], "per": [34, 36], "percent": [26, 30], "perform": [2, 3, 35, 37], "pfxacc": [16, 20], "pfxrcd": [16, 20], "phy": [4, 7, 8, 11], "pipe": [3, 35], "plain": [2, 37], "plan": 36, "pleas": 36, "png": [2, 37], "pod": [30, 36], "pod_nam": [13, 14, 16, 20, 24, 25, 26, 29, 30], "poddevicelist": [26, 30], "point": [2, 3, 35, 37], "port": [1, 36, 37], "portdescript": [7, 8], "portid": [7, 8], "portidentifi": [7, 8], "portion": [2, 37], "portkei": [8, 11], "portstatu": [7, 8], "portvalu": [8, 11], "possibl": [3, 35], "post": 36, "power": [2, 28, 37], "powersuppli": [24, 36], "powersupplykei": [24, 26], "powersupplyvalu": [24, 26], "practic": [3, 35], "preced": [2, 3, 35, 37], "precis": [2, 37], "predefin": [3, 35], "predic": [1, 37], "prefix": [3, 35], "prefixacceptedin": [16, 20], "prefixin": [16, 20], "prem": 36, "present": [2, 5, 6, 8, 37], "previou": [3, 35], "print": [3, 35], "prior": [1, 37], "prod": 36, "produc": [2, 3, 35, 37], "programmat": [3, 35], "psk": [26, 28], "psv": [26, 28], "pure": [3, 35], "push": [2, 37], "qdropcount": [8, 10], "queri": [1, 2, 34, 37], "queryparamet": [3, 35], "queue": 36, "queuesiz": [8, 10], "queuetomonitor": [8, 10], "quick": 34, "quickli": [3, 35], "quot": [3, 35], "r2": [2, 37], "rang": [2, 37], "rate": [3, 5, 6, 8, 9, 35], "rceil": [2, 37], "re": [4, 8, 13, 16, 19, 20, 21, 26], "read": [3, 35], "reassign": [3, 35], "recmap": [2, 6, 7, 8, 11, 15, 16, 24, 26], "recurs": [1, 37], "ref": [1, 2, 37], "refer": [1, 2, 37], "refid": [26, 27], "refin": [3, 35], "refindcaptur": [15, 16, 21, 26], "regex": [2, 36, 37], "region": 36, "regular": [2, 3, 35, 37], "releas": 34, "releasedeprec": [22, 26], "relnum": [22, 26], "remain": [5, 6, 8], "remaind": [3, 35], "rematch": [7, 8, 16, 20, 26, 33], "remot": [7, 8], "remotesystem": [7, 8], "remov": [1, 2, 37], "renam": [1, 37], "renamefield": [0, 14, 16], "replac": [0, 1, 2, 3, 35, 37], "request": [3, 35], "requir": [2, 7, 8, 9, 34, 37], "resource1": [26, 30], "resource2": [26, 30], "resource3": [26, 30], "rest": [3, 35], "result": [1, 2, 7, 8, 9, 10, 11, 15, 16, 20, 21, 24, 26, 28, 31, 36, 37], "return": [1, 2, 3, 35, 37], "revis": [1, 2, 8, 9, 34, 37], "rfc": [3, 35], "rfloor": [2, 37], "root": [2, 37], "round": [16, 20], "rout": [13, 19, 20, 26, 30, 36], "routing1": [26, 30], "routing2": [26, 30], "routing3": [26, 30], "rule": [3, 34, 35], "run": [3, 35], "s70": [26, 29], "s80": [26, 29], "same": [1, 2, 3, 35, 37], "scientif": [3, 35], "scope": [3, 35], "script": [2, 3, 34, 35, 37], "second": [1, 2, 3, 35, 37], "section": [2, 3, 8, 10, 35, 37], "see": [3, 35], "select": [8, 9, 11, 16, 19, 20], "selector": [3, 35], "sensorkei": [24, 26], "sensorvalu": [24, 26], "separ": [2, 3, 8, 9, 35, 37], "sequenc": [3, 35], "serial": [8, 10, 23, 36], "serial_slic": [23, 26], "servic": [2, 3, 15, 16, 35, 36, 37], "sessiondata": [13, 16, 20], "set": [1, 2, 3, 13, 16, 26, 30, 35, 37], "setfield": [0, 2, 3, 4, 8, 9, 15, 16, 20, 21, 26, 31, 35], "sever": [1, 3, 35, 37], "should": [1, 37], "show": [8, 9, 16, 20, 26, 30], "shutdown": [4, 8], "sign": [3, 35], "signal": [1, 37], "similar": [2, 3, 35, 37], "simpl": [2, 3, 35, 37], "simpli": [3, 35], "singl": [2, 3, 35, 37], "size": 36, "sku": [22, 26], "skukei": [22, 26], "skuval": [22, 26], "slash": [3, 35], "slice": [4, 7, 8, 11], "slope": [2, 37], "small": [3, 35], "smaller": [2, 37], "smash": [12, 14, 16, 17, 18, 20], "smashfdbstatu": [14, 16, 17, 18], "sn": [16, 20], "snoop": 36, "sntohostnamedict": [8, 10], "so": [1, 2, 3, 35, 37], "softwar": [22, 26], "softwarelif": [22, 26], "some": [1, 2, 3, 35, 36, 37], "somedata": [3, 35], "sourc": [1, 37], "space": [2, 37], "speci": [3, 35], "specif": [3, 16, 19, 26, 30, 34], "specifi": [1, 2, 3, 35, 37], "speed": [7, 8], "speedenum": [7, 8], "split": [2, 3, 35, 37], "spod": [25, 26], "squar": [2, 34, 37], "ss": 36, "ssj123456": [3, 35], "ssj17049015": [1, 37], "ssj17371234": [1, 37], "ssj17374660": [1, 37], "standard": [0, 2, 3, 34, 35], "star": [3, 35], "start": [1, 2, 3, 35, 37], "stat": [31, 36], "state": [1, 3, 15, 24, 26, 34, 35, 36, 37], "statement": 34, "statist": [2, 37], "statu": [3, 4, 7, 8, 12, 14, 15, 17, 18, 20, 27, 30, 32, 33, 35], "still": [1, 37], "stop": [1, 34, 37], "store": [3, 13, 16, 35], "str": [1, 2, 8, 11, 13, 14, 15, 16, 20, 22, 24, 25, 26, 29, 30, 37], "stratum": [26, 27], "strcontain": [1, 8, 11, 16, 19, 22, 23, 25, 26, 29], "strcut": [0, 23, 26], "stream": [15, 16, 36], "strfield": [2, 37], "strhasprefix": [8, 10, 23, 26], "string1": [2, 37], "string2": [2, 37], "strreplac": [8, 11, 22, 26], "strsplit": [8, 11, 22, 26], "structur": [3, 35], "subkei": [3, 35], "subscribetapath": 36, "substitut": 0, "substr": [2, 37], "subval": [3, 35], "succeed": [2, 37], "suffix": [3, 35], "sum": [5, 6, 12, 16, 17, 36], "suminoctet": [8, 9], "sumoutoctet": [8, 9], "superior": [3, 35], "support": [0, 2, 3, 22, 26, 35, 37], "sure": 36, "surround": [2, 3, 35, 37], "svi": [16, 19], "sw": [22, 26], "sweol": [22, 26], "switch": [7, 8, 22, 36], "switch_rol": [8, 11, 14, 16, 24, 26, 29, 30], "switcheol": [22, 26], "switchintfconfig": [7, 8], "switchkei": [7, 8], "switchport": [7, 8], "switchportmod": [7, 8], "switchval": [7, 8], "syntax": [0, 3, 35], "sysdb": [3, 4, 7, 8, 11, 14, 15, 16, 19, 20, 21, 24, 25, 26, 30, 32, 33, 35, 36], "sysnam": [7, 8], "system": [27, 34, 36], "t": [1, 2, 3, 35, 37], "t1": [1, 37], "t10": [1, 37], "t11": [1, 37], "t12": [1, 37], "t2": [1, 37], "t3": [1, 37], "t4": [1, 37], "t5": [1, 37], "t6": [1, 37], "t7": [1, 37], "t8": [1, 37], "t9": [1, 37], "tabl": [3, 13, 20, 26, 30, 36], "tac": 36, "tag": [1, 8, 11, 13, 20, 24, 25, 26, 29, 30, 37], "take": [2, 3, 35, 37], "tastream": [15, 16, 36], "tcam": 36, "te": [2, 37], "telemetri": 36, "temp": [2, 37], "temperatur": [2, 3, 35, 37], "temperatureperintf": [3, 35], "terminattr": [15, 16, 22, 26, 36], "terminattrstream": [15, 16, 36], "terminattrvers": [22, 26], "ternari": 34, "tertart": [2, 37], "test": [16, 20, 26, 29], "text": [2, 37], "than": [2, 3, 35, 37], "thatistext": [2, 37], "thatwastext": [2, 37], "thei": [1, 2, 3, 16, 19, 35, 36, 37], "them": [1, 2, 3, 35, 37], "therefor": [3, 35], "thi": [1, 2, 3, 13, 16, 20, 34, 35, 37], "third": [2, 37], "three": [2, 3, 35, 37], "threshold": [3, 35], "through": [2, 3, 35, 37], "time": [1, 2, 13, 16, 20, 37], "timeseri": [1, 2, 5, 6, 8, 9, 37], "timeseriesdata": [8, 10], "timest": [16, 20], "timestamp": [1, 2, 8, 10, 16, 20, 37], "token": 36, "tolow": [2, 37], "too": [2, 37], "top": [2, 37], "topk": 0, "total": [13, 16], "toupper": [2, 37], "traffic": 36, "train": [22, 26], "transceiv": 36, "treat": [2, 37], "true": [1, 2, 3, 8, 10, 16, 20, 21, 24, 26, 31, 35, 37], "truncat": [2, 37], "trunk": [7, 8], "trunkallowedvlan": [7, 8], "tseri": [16, 20], "tstamp1": [1, 2, 37], "tstamp2": [1, 2, 37], "tstamp3": [1, 2, 37], "tstamp4": [1, 2, 37], "turn": [3, 35], "twice": [3, 35], "two": [2, 3, 8, 9, 35, 37], "tx": [8, 10], "txlatenc": [8, 10], "type": [1, 2, 34, 37], "typecast": 34, "typenam": [3, 35], "typic": [2, 37], "u": [3, 35, 36], "uk": 36, "undeclar": [3, 35], "under": [1, 37], "underscor": [3, 35], "understand": [26, 31], "unfilt": [2, 37], "unicast": [16, 20], "union": [2, 37], "unit": [3, 35, 36], "unknown": [2, 37], "unknownenabledst": [4, 8], "unnesttimeseri": 0, "unspecifi": [3, 35], "unus": [8, 11], "up": [8, 9, 16, 20], "updat": [1, 2, 34, 37], "upgrad": 34, "uppercas": [2, 3, 35, 37], "url": 36, "us": [1, 2, 3, 5, 6, 8, 13, 15, 16, 26, 30, 31, 34, 35, 36, 37], "usabl": [1, 37], "usagedict": [26, 29], "usedmemoryperc": [26, 29], "usedpartitionperc": [26, 29], "util": [30, 36], "v": [3, 35], "v1": [1, 37], "v3": 36, "v5": [1, 37], "val": [3, 8, 11, 22, 26, 35], "val1": [1, 2, 3, 35, 37], "val2": [1, 2, 3, 35, 37], "val3": [1, 2, 3, 35, 37], "val4": [1, 2, 3, 35, 37], "val5": [1, 2, 3, 35, 37], "val6": [1, 37], "val7": [1, 37], "valid": [3, 35], "valu": [1, 2, 4, 5, 6, 7, 9, 11, 13, 14, 15, 16, 19, 20, 24, 25, 26, 28, 29, 30, 31, 34, 36, 37], "valueifconditionisfals": [3, 35], "valueifconditionistru": [3, 35], "valuetocast": [3, 35], "var": [16, 20, 36], "vari": [3, 35], "variabl": [0, 1, 2, 3, 7, 8, 26, 27, 30, 35, 37], "variou": [3, 13, 16, 20, 35], "varset": [3, 35], "vendorsn": [26, 32, 33], "verbos": [1, 37], "veri": [1, 3, 35, 37], "version": [2, 3, 5, 6, 8, 9, 10, 13, 16, 20, 21, 22, 26, 28, 29, 31, 35, 37], "vf": [26, 31], "vlan": [7, 8, 14, 15, 36], "vlanconfig": [14, 16, 25, 26], "vlankei": [15, 16, 25, 26], "vlanstatu": [15, 16], "vlanvalu": [25, 26], "voltag": [2, 37], "vrf": [14, 20, 36], "vrfbgppeerinfostatusentryt": [16, 20], "vrfconfig": [14, 16], "vrfcount": [14, 16], "wa": [1, 2, 3, 4, 8, 35, 37], "want": [1, 2, 3, 35, 37], "wcname": [3, 35], "we": [1, 13, 16, 20, 37], "web": [2, 37], "webinar03": 36, "week": [3, 35], "weight": [2, 37], "well": [3, 35], "were": [1, 3, 26, 31, 35, 37], "what": [2, 3, 35, 37], "when": [1, 3, 35, 37], "where": [2, 3, 5, 6, 8, 9, 11, 12, 13, 14, 16, 17, 20, 24, 25, 26, 29, 30, 31, 33, 35], "whether": [2, 37], "which": [1, 2, 3, 35, 37], "whole": [3, 35], "whose": [1, 2, 37], "widcard": [5, 6, 8], "wide": [3, 35], "wildcard": [0, 1, 2, 34, 37], "wildcardnam": [3, 35], "wipe": [1, 37], "within": [2, 3, 35, 37], "without": [2, 3, 35, 37], "work": [1, 2, 3, 8, 9, 34, 35, 37], "world": [3, 35], "would": [1, 2, 3, 35, 36, 37], "write": [2, 3, 35, 37], "written": [1, 3, 35, 37], "wtih": 36, "www": 36, "x": [2, 37], "xcvr": [2, 3, 26, 32, 33, 35, 37], "xcvrstatu": [26, 32, 33], "xt": [2, 37], "y": [2, 37], "ye": [3, 35], "yet": [13, 16], "you": [3, 35], "z": [2, 37], "zero": [13, 16, 36]}, "titles": ["Language revisions / Change log", "field", "General functions", "Quick overview", "Admin state of interfaces per device", "Count interfaces with non-zero outbound OR inbound traffic (with key existence check)", "Count interfaces with non-zero outbound traffic", "Interface Information wtih a filter on Port Description", "Interface States Examples", "Interface counters sum per interface list per device", "LANZ queue Size information filtering the null-values", "Port Utilization", "Number of ARP entries across all devices", "BGP States", "Capacity Planning Routing and Switching", "IGMP Snooping Table", "Layer 2 and Layer 3 examples", "Number of MAC addresses across all devices", "Number of MACs per device per interface", "List of Configured VLANs per VRFs", "TAC Webinar03 2023 - BGP States", "Listing of devices affected by FN44", "EoL Planning", "Listing of devices affected by FN72", "Hardware Health Check", "Important Pod Count", "System Health Examples", "NTP Stats", "PowerSupply Output", "System Health Check", "TCAM Capacity", "Listing Devices that have a file in /var/core", "List the serial numbers for all transceivers", "List the transceiver serial numbers that match the input regex", "AQL Documentation", "Language Specification", "AQL Examples", "AQL Standard Library"], "titleterms": {"2": [0, 16], "2023": [16, 20], "3": [0, 16], "4": 0, "7020r": [14, 16, 26, 30], "7050x3": [14, 16, 26, 30], "7280r2": [14, 16, 26, 30], "For": [3, 35], "If": [3, 35], "No": [3, 35], "Not": [13, 16], "OR": [5, 8], "ab": [2, 37], "across": [12, 16, 17], "addit": [3, 35], "address": [16, 17], "admin": [4, 8], "affect": [21, 23, 26], "aggreg": [2, 37], "all": [12, 13, 16, 17, 26, 32], "analysi": [2, 37], "applydelet": [1, 37], "aql": [34, 36, 37], "ar": [13, 16], "arp": [12, 16], "basic": [3, 35], "behaviour": [3, 35], "bgp": [13, 16, 20], "bool": [3, 35], "boolean": [3, 35], "bottomk": [1, 37], "bracket": [3, 35], "capac": [14, 16, 26, 30], "ceil": [2, 37], "chang": 0, "check": [5, 8, 24, 26, 29], "cli": [2, 37], "cloudvis": 0, "comparison": [3, 35], "complex": [3, 35], "complexkei": [2, 37], "concaten": [3, 35], "configur": [16, 19], "content": [34, 35, 36, 37], "core": [26, 31], "count": [5, 6, 8, 25, 26], "counter": [8, 9], "cpu": [26, 29], "data": [2, 37], "dataset": [3, 35], "deepmap": [1, 37], "default": [3, 13, 16, 35], "delet": [2, 37], "densiti": [14, 16], "descript": [7, 8], "detail": [13, 16], "devic": [4, 8, 9, 12, 13, 16, 17, 18, 21, 23, 24, 26, 31], "dhistogram": [2, 37], "dict": [2, 3, 35, 37], "dicthaskei": [2, 37], "dictkei": [2, 37], "dictremov": [2, 37], "direct": [3, 35], "divis": [3, 35], "dkurtosi": [2, 37], "dmean": [2, 37], "dmedian": [2, 37], "document": 34, "dpercentil": [2, 37], "dskew": [2, 37], "dstddev": [2, 37], "dsum": [2, 37], "dump": [2, 37], "durat": [3, 35], "dvarianc": [2, 37], "element": [3, 35], "els": [3, 35], "entri": [12, 16], "eol": [22, 26], "equal": [2, 37], "establish": [13, 16], "evpn": [14, 16], "ewlinregress": [2, 37], "exampl": [8, 16, 26, 36], "execut": [3, 35], "exist": [5, 8], "exp": [2, 37], "express": [3, 35], "fabric": [26, 29], "factori": [2, 37], "fan": [24, 26], "field": [1, 37], "file": [26, 31], "filter": [3, 7, 8, 10, 35, 37], "fix": [3, 35], "flap": [16, 20], "flash": [26, 29], "floodlist": [25, 26], "floor": [2, 37], "fn44": [21, 26], "fn72": [23, 26], "formatfloat": [2, 37], "formatint": [2, 37], "function": [2, 3, 35, 37], "gatewai": [14, 16], "gcd": [2, 37], "gener": [2, 37], "groupbi": [2, 37], "hardwar": [24, 26], "have": [26, 31], "hdl": [14, 16], "health": [24, 26, 29], "help": [2, 37], "high": [14, 16], "histogram": [2, 37], "histor": [16, 20], "igmp": [15, 16], "import": [25, 26], "inbound": [5, 8], "inform": [7, 8, 10], "input": [3, 26, 33, 35], "interfac": [4, 5, 6, 7, 8, 9, 16, 18], "kei": [5, 8], "keyword": [3, 35], "kurtosi": [2, 37], "label": [14, 16], "languag": [0, 3, 35], "lanz": [8, 10], "layer": 16, "ldl": [14, 16], "leaf": [14, 16, 25, 26], "length": [2, 37], "librari": 37, "linregress": [2, 37], "list": [3, 8, 9, 16, 19, 21, 23, 26, 31, 32, 33, 35], "load": [2, 37], "log": [0, 2, 37], "log10": [2, 37], "loop": [3, 35], "low": [14, 16], "mac": [16, 17, 18], "manipul": [2, 37], "manual": [3, 35], "map": [1, 3, 35, 37], "mapn": [1, 37], "match": [26, 33], "math": [2, 37], "max": [2, 25, 26, 37], "mean": [2, 37], "median": [2, 37], "memori": [26, 29], "merg": [2, 3, 35, 37], "messag": [16, 20], "min": [2, 37], "mnt": [26, 29], "modulo": [3, 35], "multipl": [3, 35], "name": [3, 35], "newdict": [2, 37], "non": [5, 6, 8], "note": [3, 35], "now": [2, 37], "ntp": [26, 27], "null": [8, 10], "num": [3, 35], "number": [3, 12, 14, 16, 17, 18, 25, 26, 32, 33, 35], "numer": [3, 35], "onli": [2, 37], "oper": [3, 35], "outbound": [5, 6, 8], "output": [26, 28], "overview": [3, 35], "paramet": [3, 35], "path": [3, 35], "per": [0, 3, 4, 8, 9, 16, 18, 19, 24, 26, 35], "percentil": [2, 37], "plan": [14, 16, 22, 26], "plot": [2, 37], "pod": [25, 26], "port": [7, 8, 11], "pow": [2, 37], "power": [3, 24, 26, 35], "powersuppli": [26, 28], "queri": [3, 35], "queue": [8, 10], "quick": [3, 35], "rang": [3, 35], "rate": [2, 37], "recmap": [1, 37], "refindal": [2, 37], "refindcaptur": [2, 37], "regex": [26, 33], "releas": 0, "rematch": [2, 37], "renamefield": [1, 37], "resampl": [1, 37], "result": [3, 35], "revis": 0, "round": [2, 37], "rout": [14, 16], "sensor": [24, 26], "serial": [26, 32, 33], "session": [13, 16, 20], "setfield": [1, 37], "size": [8, 10, 25, 26], "skew": [2, 37], "snoop": [15, 16], "specif": 35, "sqrt": [2, 37], "squar": [3, 35], "standard": 37, "stat": [2, 26, 27, 37], "state": [4, 8, 13, 16, 20], "statement": [3, 35], "statu": [13, 16, 24, 26], "stddev": [2, 37], "str": [3, 35], "strcontain": [2, 37], "strcount": [2, 37], "strcut": [2, 37], "strhasprefix": [2, 37], "strhassuffix": [2, 37], "strindex": [2, 37], "string": [2, 3, 35, 37], "strreplac": [2, 37], "strsplit": [2, 37], "strtolow": [2, 37], "strtoupper": [2, 37], "subtract": [3, 35], "sum": [2, 8, 9, 37], "summari": [16, 20], "suppli": [24, 26], "switch": [14, 16, 25, 26, 30], "syslog": [16, 20], "system": [26, 29], "tabl": [15, 16, 35, 37], "tac": [16, 20], "tag": [14, 16], "tcam": [26, 30], "temperatur": [24, 26], "ternari": [3, 35], "time": [3, 35], "timeseri": [3, 35], "timestamp": [3, 35], "topk": [1, 37], "total": [25, 26], "tracker": [16, 20], "traffic": [5, 6, 8], "transceiv": [26, 32, 33], "trunc": [2, 37], "type": [3, 35], "typecast": [3, 35], "unknown": [3, 35], "unnesttimeseri": [2, 37], "updat": [3, 35], "usag": [26, 29], "user": [3, 35], "util": [8, 11, 26, 29], "valu": [3, 8, 10, 35], "var": [26, 31], "varianc": [2, 37], "vlan": [16, 19, 25, 26], "vrf": [13, 16, 19], "vtep": [25, 26], "webinar03": [16, 20], "where": [1, 37], "while": [3, 35], "wildcard": [3, 35], "wtih": [7, 8], "zero": [5, 6, 8]}}) \ No newline at end of file