Introduction
In a multi‑tenant data‑center fabric, global tunnel counts and spine‑level health (CPU, memory, link utilization < 30 %) can appear normal while tenants experience intermittent packet loss and latency spikes. This article shows how to detect partial tenant degradation—when only a subset of tunnels for a specific tenant is impaired—by using tenant‑scoped telemetry, correlated timelines, and the signals that separate local damage from fabric‑wide failure.
Understanding Tenant‑Specific Telemetry
Tenant‑specific telemetry is any measurement tagged or scoped to a logical tenant construct (VRF, VPN‑ID, tenant‑ID, etc.). In model‑driven telemetry this is achieved by:
- Adding tenant‑identifying keys to the data model (e.g., augmenting
ietf-te:tunnelwithtenant-id). - Exporting per‑tenant counters via gNMI subscriptions on paths such as
/network-instances/network-instance[name=Tenant-A]/protocols/protocol[bgp]/bgp/peer. - Streaming per‑tenant queue depth, latency histograms, and loss ratios from line‑cards or switches.
If the platform does not natively export tenant‑scoped counters, they can be derived by post‑processing raw interface or tunnel metrics with the tenant‑to‑tunnel mapping (often available from the orchestrator or IPAM).
Missing signal note: When only aggregate tunnel counters are exported without a tenant label, per‑tenant loss must be inferred from flow‑sampling or sFlow data, which introduces sampling error.
Importance of Correlated Timelines
A correlated timeline aligns multiple data streams on a common time axis, revealing cause‑effect relationships invisible when each stream is viewed in isolation. For tenant degradation detection the timeline typically includes:
- Tenant‑scoped metrics (latency, loss, jitter, tunnel flap count).
- Global fabric health (spine CPU, link utilization, BGP peer state).
- Application logs/request traces (timestamped latency or error codes).
- Control‑plane events (configuration changes, ACL updates, QoS policy pushes).
Overlaying these streams lets you answer questions such as:
- Did the latency spike coincide with a change in the tenant’s QoS policy?
- Did a burst of micro‑bursts on a specific leaf‑to‑leaf link appear only for tunnels belonging to the affected tenant?
If fabric‑wide events show no correlation with tenant‑specific symptoms, the problem is likely local to the tenant’s data path.
Identifying Partial Tenant Degradation
Analyzing Global Tunnel Counts and Spine Health
Global tunnel count (tunnel_up_total) and spine health metrics (e.g., spine_cpu_utilization, spine_link_utilization) are aggregate signals. They mask heterogeneity because:
- A small subset of troubled tunnels can be diluted by thousands of healthy tunnels.
- Spine CPU may stay low even if a few leaf‑to‑leaf paths are congested, as the spine forwards traffic in hardware and sees only packet counts, not per‑flow queuing.
PromQL example (assuming the metrics exist):
# Global tunnel up count – looks stable
sum by (job) (tunnel_up_total)
# Spine CPU – also stable
avg by (instance) (spine_cpu_utilization)
Both queries return flat lines during an incident, giving a false sense of health.
Limitations of Global Metrics
| Limitation | Why it matters for tenant degradation |
|---|---|
| Aggregation bias | High‑impact, low‑volume events are averaged out. |
| Lack of tenant dimension | No way to isolate a specific VRF or VPN. |
| Static thresholds | Global thresholds may be too lenient for a tenant’s SLA. |
| No per‑path latency | Spine health does not capture leaf‑to‑leaf queuing delay. |
Role of Tenant‑Scoped Telemetry
Tenant‑scoped telemetry provides the granularity needed to see the tail of the distribution. Key metrics include:
tenant_tunnel_latency_seconds{tenant="Tenant-A", direction="egress"}– histogram or summary.tenant_tunnel_loss_ratio{tenant="Tenant-A"}– packets lost / packets sent.tenant_queue_depth_packets{tenant="Tenant-A", queue="priority-0"}– depth per priority queue.tenant_tunnel_flap_count{tenant="Tenant-A"}– number of state changes per interval.
When any of these diverge from baseline while global metrics stay nominal, you have a partial tenant degradation signal.
Tenant‑Scoped Telemetry and Metrics
Overview of Relevant Metrics
| Metric | Type | Typical Unit | What it reveals |
|---|---|---|---|
tenant_tunnel_latency_seconds | Histogram | seconds | Tail latency (p95, p99) per tenant. |
tenant_tunnel_loss_ratio | Gauge | ratio (0‑1) | Packet loss specific to tenant tunnels. |
tenant_tunnel_jitter_seconds | Gauge | seconds | Variation in latency – useful for real‑time apps. |
tenant_queue_depth_packets | Gauge | packets | Congestion indicator per priority queue. |
tenant_tunnel_flap_count | Counter | events | Instability in tunnel state (possible mis‑config). |
tenant_acl_drop_packets | Counter | packets | Drops due to tenant‑specific ACLs or firewall rules. |
tenant_telemetry_export_rate | Gauge | samples/sec | Health of the telemetry pipeline itself (detects export throttling). |
If the platform does not export these metrics directly, compute them from raw counters using the tenant‑to‑tunnel mapping (see CLI examples below).
Configuring and Collecting Tenant‑Scoped Telemetry
Model‑driven approach (gNMI)
- Define a subscription that includes tenant‑identifying keys. Example gNMI path (JSON‑encoded):
{
"subscription": [
{
"path": {
"elem": [
{ "name": "network-instances" },
{ "name": "network-instance", "key": { "name": "Tenant-A" } },
{ "name": "protocols" },
{ "name": "protocol", "key": { "identifier": "bgp", "name": "default" } },
{ "name": "bgp" },
{ "name": "peer" },
{ "name": "state" }
]
},
"mode": "SAMPLE",
"sample_interval": 10000000000 // 10 s in nanoseconds
},
{
"path": {
"elem": [
{ "name": "interfaces" },
{ "name": "interface", "key": { "name": "tunnel100" } },
{ "name": "counters" }
]
},
"mode": "SAMPLE",
"sample_interval": 5000000000 // 5 s
}
],
"encoding": "PROTO",
"heartbeat_interval": 30000000000 // 30 s
}
- Start the subscription using
gnmic:
gnmic -a leaf1:9339 -u admin -p \
--encoding proto \
subscribe \
--subscription-path ./tenant-a-subscription.json
The output can be piped to a Prometheus remote‑write endpoint or a time‑series database (e.g., Cortex, Thanos) for downstream querying.
CLI‑based collection (when gNMI not available)
On many NOS platforms you can fetch per‑tenant tunnel stats via CLI and push them to a monitoring system via a script. Example on a hypothetical netos CLI:
# 1. Get list of tunnels belonging to Tenant-A
show tunnel brief | include Tenant-A | awk '{print $1}' > /tmp/tenant-a-tunnels.txt
# 2. For each tunnel, collect latency and loss
while read tun; do
latency=$(show tunnel $tenu detail | grep "Avg Latency" | awk '{print $3}')
loss=$(show tunnel $tenu detail | grep "Packet Loss" | awk '{print $3}')
echo "tenant_tunnel_latency_seconds{tenant=\"Tenant-A\",tunnel=\"$tun\"} $latency"
echo "tenant_tunnel_loss_ratio{tenant=\"Tenant-A\",tunnel=\"$tun\"} $loss"
done < /tmp/tenant-a-tunnels.txt
Run the script every 30 s via cron and push the formatted lines to a Pushgateway or directly to Prometheus via the textfile collector.
Example CLI Commands for Data Collection
| Platform | Command | Purpose |
|---|---|---|
| Junos | `show interfaces terse | match “lt-”` |
| Cisco IOS XR | show tunnel tenants Tenant-A | (hypothetical) direct tenant‑scoped tunnel view. |
| Arista EOS | `show interfaces tunnel | include Tenant-A` |
| Linux (VPP) | `vppctl show interface | grep tenant-a` |
| Generic | curl -s http://localhost:9090/api/v1/query?query=tenant_tunnel_loss_ratio{tenant=\"Tenant-A\"} | Query Prometheus for the latest loss ratio. |
Correlating Timelines for Root Cause Analysis
Understanding Correlated Timelines
A correlated timeline is a time‑aligned multi‑series plot where each series represents a different telemetry dimension. Its power lies in visual inspection (or algorithmic correlation) to detect:
- Temporal precedence – does a change in metric A lead to a change in metric B?
- Co‑occurrence – do two anomalies appear within the same window?
- Absence of correlation – when fabric metrics stay flat while tenant metrics deviate, indicating a local issue.
Tools and Techniques
| Tool | How it helps | Example usage |
|---|---|---|
| Grafana (with Prometheus/Loki) | Overlay panels; use align and range queries to align timestamps. | Panel A: tenant_tunnel_latency_seconds{tenant="Tenant-A"}; Panel B: spine_cpu_utilization. |
| Grafana Tempo + Loki | Correlate traces and logs with metrics via traceID or log timestamps. | Search Loki for tenant=Tenant-A and level=error; join with Tempo traces to see if latency spikes match trace durations. |
| Python/pandas | Programmatic alignment, cross‑correlation, lag detection. | Load CSV exports from Prometheus, resample to 1 s, compute Pearson correlation with lag. |
| Apache Flink / Spark Structured Streaming | Real‑time windowed joins between metric streams and event streams. | Join tenant_tunnel_loss stream with acl_update event stream on event time; flag when loss rises > 5 % within 2 s of an ACL push. |
| Elasticsearch APM | Automatic correlation of application latency with infrastructure metrics. | View APM service map; overlay custom metric tenant_tunnel_loss_ratio. |
Example Code for Automating Timeline Correlation
import requests
import pandas as pd
import numpy as np
PROM_URL = "http://prometheus:9090/api/v1/query_range"
def fetch_series(query, start, end, step="5s"):
params = {"query": query, "start": start, "end": end, "step": step}
r = requests.get(PROM_URL, params=params)
r.raise_for_status()
data = r.json()["data"]["result"]
values = np.array([float(v[1]) for v in data[0]["values"]])
times = pd.to_datetime([v[0] for v in data[0]["values"]], unit="s")
return pd.Series(values, index=times)
# Time window of interest (e.g., last 2 h)
now = pd.Timestamp.utcnow()
start = now - pd.Timedelta(hours=2)
end = now
latency = fetch_series(
'histogram_quantile(0.95, sum by (le,tenant) (rate(tenant_tunnel_latency_seconds_bucket{tenant="Tenant-A"}[30s])))',
start.timestamp(), end.timestamp()
)
spine_cpu = fetch_series(
'avg by (instance) (spine_cpu_utilization{job="spine"})',
start.timestamp(), end.timestamp()
)
# Align on common index (inner join)
df = pd.DataFrame({"latency": latency, "spine_cpu": spine_cpu}).dropna()
# Cross‑correlation for lags -30..+30 steps (±2.5 min)
lags = np.arange(-30, 31)
corrs = [df["latency"].corr(df["spine_cpu"].shift(lag)) for lag in lags]
best_lag = lags[np.argmax(np.abs(corrs))]
best_corr = np.max(np.abs(corrs))
print(f"Best lag: {best_lag} steps ({best_lag*5}s) with correlation {best_corr:.3f}")
if best_lag < 0:
print("Latency leads spine CPU (possible local issue).")
else:
print("Spine CPU leads latency (fabric‑wide effect).")
Interpretation: A negative lag (tenant latency changes before spine CPU) with moderate correlation suggests the latency spike is not driven by spine load but by something local to the tenant’s path.
Distinguishing Local Damage from Fabric‑Wide Failure
| Signal | Local Damage (Tenant‑Specific) | Fabric‑Wide Failure |
|---|---|---|
| Tenant‑scoped latency/jitter | ↑ p95/p99 latency, ↑ jitter for only affected tenant(s). | ↑ latency/jitter across all tenants (or a large fraction). |
| Tenant‑scoped loss ratio | ↑ loss for specific tenant tunnels. | ↑ loss globally or across many tenants. |
| Queue depth (per‑priority) | Build‑up in one or a few queues for the tenant. | Uniform increase across all tenants/queues. |
| Tunnel flap count | Spikes in flaps for tunnels belonging to the tenant. | Widespread flap increase across many tenants. |
| Spine CPU / link utilization | Remains flat or shows only minor variation. | Noticeable rise correlating with tenant symptoms. |
| Control‑plane events (ACL/QoS changes) | Tenant‑specific policy updates precede symptoms. | Broad policy changes affecting multiple tenants. |
| Application logs | Errors/l latency spikes correlate with tenant‑specific request timestamps. | Errors appear across unrelated applications/tenants. |
When tenant‑scoped metrics deviate while global fabric indicators stay within normal bounds, the evidence points to local damage—a problem confined to the tenant’s data path (e.g., mis‑queuing, ACL drop, micro‑burst on a leaf‑to‑leaf link). Conversely, concurrent deviations in both tenant‑scoped and fabric‑wide metrics suggest a fabric‑wide failure.
By leveraging tenant‑scoped telemetry, correlating timelines, and interpreting the signals above, operators can quickly isolate partial tenant degradation and avoid lengthy, misguided fabric‑centric troubleshooting.