Skip to main content

Grafana Integration & Analytics Guide

Building Operational Dashboards and Alerts for RAN Monitoring

Complete guide to Grafana dashboarding, alerting strategies, and KPI visualization


Table of Contents

  1. Overview
  2. Grafana & InfluxDB Setup
  3. Data Source Configuration
  4. Dashboard Design Patterns
  5. Query Examples
  6. Alert Rules & Escalation
  7. Operational Dashboards
  8. Troubleshooting

Overview

Grafana is a visualization and alerting platform that transforms the metrics collected by RAN Monitor into actionable insights for network operations teams.

Monitoring Architecture

Grafana Benefits

  • Real-Time Visibility - Live dashboards showing current network state
  • Historical Analysis - Trend analysis over days/weeks/months
  • Alerting - Proactive notifications before issues impact users
  • Custom Views - Dashboards tailored to different roles (exec, ops, engineering)
  • Reporting - Snapshot exports and scheduled reports

Dashboard Customization

Important: The dashboards and visualizations described in this guide are examples and templates. The Operations/NOC (ONS) team will design and build Grafana dashboards according to their specific operational requirements, KPIs, and monitoring workflows.

This guide provides:

  • Query examples and patterns to build upon
  • Best practices for dashboard organization
  • Alert configuration templates
  • Counter reference mappings (see Nokia Counter Reference)

The ONS team should customize:

  • Panel layouts and visualizations
  • Alert thresholds and escalation policies
  • Retention policies for their data volume (see Data Retention Policy)
  • Aggregation windows based on monitoring needs
  • Notification channels and routing

For runtime configuration options and data collection settings, refer to the Runtime Configuration Guide.


Grafana & InfluxDB Setup

Installation

Prerequisites:

  • InfluxDB 2.0+ with bucket created for RAN Monitor
  • InfluxDB API token with read permissions
  • Network connectivity between Grafana and InfluxDB

Docker Compose Example:

version: '3.8'
services:
influxdb:
image: influxdb:2.7
environment:
INFLUXDB_DB: ran_metrics
INFLUXDB_ADMIN_USER: admin
INFLUXDB_ADMIN_PASSWORD: change_me
ports:
- "8086:8086"
volumes:
- influxdb_data:/var/lib/influxdb2

grafana:
image: grafana/grafana:latest
environment:
GF_SECURITY_ADMIN_PASSWORD: change_me
ports:
- "3000:3000"
depends_on:
- influxdb
volumes:
- grafana_data:/var/lib/grafana
- ./provisioning:/etc/grafana/provisioning

volumes:
influxdb_data:
grafana_data:

Creating an InfluxDB API Token

  1. Open InfluxDB UI (port 8086)
  2. Navigate to API Tokens
  3. Create new token with permissions:
    • Read: buckets, ran_metrics (your bucket)
  4. Copy token value
  5. Use in Grafana data source configuration

Data Source Configuration

Adding InfluxDB as Data Source in Grafana

  1. Access Data Sources

    • Grafana → Configuration → Data Sources
  2. Create New Data Source

    • Click "Add data source"
    • Select "InfluxDB"
  3. Configure Connection

    SettingValueNotes
    NameRAN MonitorDisplay name in Grafana
    URLhttp://influxdb:8086Must be reachable from Grafana
    AccessServer (default)Grafana backend accesses DB
    OrganizationomnitouchYour InfluxDB org
    Token(API token)From API token creation
    Default Bucketran_metricsWhere RAN Monitor writes
    Min time interval10sMatches polling interval
  4. Test Connection

    • Click "Test" button
    • Should show "DataSource is working"

Note: The InfluxDB connection settings (URL, organization, bucket name) must match your RAN Monitor configuration. See Runtime Configuration Guide for details on InfluxDB setup and AirScale Configuration for base station registration.

Flux Query Language

Grafana uses Flux to query InfluxDB. Basic syntax:

from(bucket:"ran_metrics")
|> range(start: -7d, stop: now())
|> filter(fn: (r) => r._measurement == "PerformanceMetrics")
|> filter(fn: (r) => r.device == "SITE_A_BS1")
|> group(by: ["_field"])
|> aggregateWindow(every: 1h, fn: mean)

Key Concepts:

  • from() - Select bucket
  • range() - Time window
  • filter() - Select data
  • group() - Organize results
  • aggregateWindow() - Summarize time periods

Dashboard Design Patterns

Dashboard Hierarchy

Panel Types & Use Cases

Standard Dashboard Sections

Top Section: Key Metrics (Status Indicators)

Show current state at a glance:

┌─────────────────────────────────────────────────┐
│ Network Health Snapshot │
├──────────────┬──────────────┬──────────────────┤
│ Devices Up │ Active Alarms │ Avg Cell Avail │
│ 48/50 (96%) │ 3 Critical │ 98.5% │
├──────────────┴──────────────┴──────────────────┤
│ Most Recent Incident: [2 hours ago] Fixed │
└─────────────────────────────────────────────────┘

Purpose:

  • Quick status verification (< 10 seconds to assess)
  • Green/red indicators for immediate issues
  • Links to detailed dashboards for investigation

Show patterns and changes over time:

┌─────────────────────────────────────────────────┐
│ Traffic Patterns (7 days) │
│ [Large area chart with daily/weekly patterns] │
│ Peak: 250 Gbps (Wednesday 2pm) │
│ Valley: 80 Gbps (Sunday 3am) │
└─────────────────────────────────────────────────┘

Purpose:

  • Identify capacity constraints
  • Understand traffic patterns
  • Predict peak times
  • Detect anomalies

Bottom Section: Details & Alerts (Tables)

Show granular information:

┌─────────────────────────────────────────────────┐
│ Active Alarms (Sorted by Severity) │
├───────┬──────────────┬────────────┬────────────┤
│ Level │ Device │ Issue │ Duration │
├───────┼──────────────┼────────────┼────────────┤
│ 🔴 │ SITE_A_BS1 │ Cell Down │ 45 minutes │
│ 🟠 │ SITE_B_BS2 │ High Temp │ 2 hours │
└─────────────────────────────────────────────────┘

Purpose:

  • Immediate action items
  • Investigation details
  • Trend information (duration, frequency)

Query Examples

Note: The following query examples use Nokia-specific performance counters. For detailed counter definitions, units, and usage guidelines, refer to the Nokia Counter Reference. For configuring data collection intervals and InfluxDB settings, see the Runtime Configuration Guide.

Performance Metrics Queries

Cell Availability by Device (Last 24 Hours)

from(bucket:"ran_metrics")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "PerformanceMetrics")
|> filter(fn: (r) => r._field == "CellAvailability")
|> group(by: ["device"])
|> aggregateWindow(every: 1h, fn: mean)
|> yield(name: "cell_availability")

Usage:

  • Executive dashboard for SLA reporting
  • Time series chart showing hourly averages
  • Target: > 99.5% availability

Traffic Throughput Trend (7 Days)

from(bucket:"ran_metrics")
|> range(start: -7d)
|> filter(fn: (r) => r._measurement == "PerformanceMetrics")
|> filter(fn: (r) => r._field =~ /Throughput.*/)
|> group(by: ["device", "_field"])
|> aggregateWindow(every: 10m, fn: mean)
|> yield(name: "traffic_trend")

Usage:

  • Capacity planning dashboard
  • Area chart showing peak vs. valley
  • Identify busy hours for scheduling

DL Resource Utilization by Cell

from(bucket:"ran_metrics")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "PerformanceMetrics")
|> filter(fn: (r) => r._field == "DLResourceUtilization")
|> filter(fn: (r) => r.device == "SITE_A_BS1")
|> aggregateWindow(every: 10s, fn: last)
|> yield(name: "dl_resource")

Usage:

  • Real-time operations dashboard
  • Gauge panel warning at 80%, critical at 95%
  • Quick identification of congested cells

Alarm Queries

Active Alarms by Severity (Last 24 Hours)

from(bucket:"ran_metrics")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "Alarms")
|> filter(fn: (r) => r.status == "active")
|> group(by: ["severity"])
|> count()
|> yield(name: "alarm_count")

Usage:

  • Status indicator showing alarm counts
  • Pie chart of distribution
  • Click-through to detailed alarm list

Alarm Rate (Alarms per Hour)

from(bucket:"ran_metrics")
|> range(start: -7d)
|> filter(fn: (r) => r._measurement == "Alarms")
|> group(by: ["severity"])
|> aggregateWindow(every: 1h, fn: count)
|> yield(name: "alarm_rate")

Usage:

  • Trend chart showing when alarm storms occur
  • Identify times of high instability
  • Correlate with configuration changes

Frequently Triggered Alarms

from(bucket:"ran_metrics")
|> range(start: -7d)
|> filter(fn: (r) => r._measurement == "Alarms")
|> group(by: ["alarm_description"])
|> count()
|> sort(columns: ["_value"], desc: true)
|> limit(n: 10)
|> yield(name: "top_alarms")

Usage:

  • Identify systemic issues
  • Prioritize engineering efforts
  • Root cause analysis focus

Advanced Analytics

Cell Availability Forecast (Linear Regression)

from(bucket:"ran_metrics")
|> range(start: -30d)
|> filter(fn: (r) => r._measurement == "PerformanceMetrics")
|> filter(fn: (r) => r._field == "CellAvailability")
|> filter(fn: (r) => r.device == "SITE_A_BS1")
|> aggregateWindow(every: 1h, fn: mean)
|> statefulWindow(every: 1h, period: 24h)
|> map(fn: (r) => ({r with _value: float(v: r._value)}))
|> reduce(fn: (r, acc) => ({
x: acc.x + [float(v: r._time)],
y: acc.y + [r._value]
}),
initial: {x: [], y: []})
|> yield(name: "availability_forecast")

Usage:

  • Predict when SLA may be breached
  • Proactive maintenance scheduling
  • Capacity forecasting

Handover Success Correlation with Traffic

from(bucket:"ran_metrics")
|> range(start: -7d)
|> filter(fn: (r) => r._measurement == "PerformanceMetrics")
|> filter(fn: (r) => r._field =~ /HandoverSuccess|Traffic/)
|> group(by: ["device", "_field"])
|> aggregateWindow(every: 1h, fn: mean)
|> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
|> map(fn: (r) => ({r with correlation: float(v: r.HandoverSuccess) * float(v: r.Traffic)}))
|> yield(name: "ho_traffic_correlation")

Usage:

  • Identify if handover issues are load-related
  • Tune handover hysteresis thresholds
  • Network optimization insights

Alert Rules & Escalation

Alert Strategy Framework

Creating Alert Rules in Grafana

Step 1: Create Alert Rule

  1. Open Dashboard
  2. Click panel to alert on
  3. Panel → Create alert
  4. Or Alerting → Alert rules → Create new alert rule

Step 2: Configure Evaluation Criteria

Example 1: Cell Availability Alert

Condition: CellAvailability < 95%
Duration: 15 minutes
Evaluation Frequency: Every 1 minute
For: The last 15 minutes

Rationale:

  • Trigger at 95% to warn before SLA breach (99.5%)
  • 15-minute window to avoid false positives from transients
  • Monitor every minute for rapid response

Example 2: Alarm Storm Detection

Condition: count(active_alarms) > 10
Duration: 5 minutes
Evaluation Frequency: Every 2 minutes
For: The last 5 minutes

Rationale:

  • 10+ alarms indicate systemic issue
  • Quick 5-minute detection for rapid response
  • Check frequently to catch escalation

Example 3: DL Resource Exhaustion

Condition: DLResourceUtilization > 90%
Duration: 30 minutes
Evaluation Frequency: Every 5 minutes
For: The last 30 minutes

Rationale:

  • Sustained high resource usage indicates congestion
  • 30-minute window prevents false alerts from traffic spikes
  • Monitor every 5 minutes to catch sustained congestion

Step 3: Configure Notification Channel

  1. Click "Notification channel"
  2. Select or create channel (Slack, Email, PagerDuty, etc.)
  3. Configure message template

Message Template Example:

Alert: {{ .AlertRuleName }}
Severity: {{ .Severity }}
Device: {{ .Labels.device }}
Value: {{ .EvalMatches[0].Value }}
Duration: {{ .StartsAt }}

{{ .RuleUrl }}

Escalation Policies

Tier 1 Alerts (Critical):

  • Condition: Service impacting (device down, SLA breach imminent)
  • Duration: Immediate (1-5 minutes)
  • Notification: Phone call + SMS + Slack + PagerDuty
  • Owner: On-call engineer
  • SLA: Response in < 15 minutes

Tier 2 Alerts (Major):

  • Condition: Degraded performance (quality, availability trending down)
  • Duration: 15-30 minutes
  • Notification: Slack + Email + PagerDuty
  • Owner: NOC team + senior engineer
  • SLA: Response in < 30 minutes

Tier 3 Alerts (Minor):

  • Condition: Informational (trends, approaching limits)
  • Duration: 1+ hours
  • Notification: Slack + Dashboard
  • Owner: Capacity planning / engineering
  • SLA: Daily review

Notification Channels

Slack Integration

1. Create Slack App in workspace
2. Get webhook URL
3. In Grafana Alerting → Notification channels
4. Add "Slack" channel
5. Paste webhook URL
6. Test notification

Slack Message Formatting:

🔴 CRITICAL: Cell Down - SITE_A_BS1_Cell1
Duration: 45 minutes
Impact: ~2000 subscribers
Last successful data: 2:15 PM

[Investigate] [Acknowledge] [Dashboard]

PagerDuty Integration

1. Create integration key in PagerDuty
2. In Grafana Alerting → Notification channels
3. Add "PagerDuty" channel
4. Paste integration key
5. Map severity levels:
- Critical → Trigger incident
- Major → Trigger with lower urgency
- Minor → Add to existing incident

Email Integration

1. Configure SMTP in Grafana config
2. In Alerting → Notification channels
3. Add "Email" channel
4. Enter recipient addresses
5. Can include CSV of recipients for distribution lists

Operational Dashboards

Dashboard 1: Executive Health Dashboard

Audience: Management, executives Refresh Rate: 5 minutes Purpose: High-level health overview

Panels:

  1. Status Summary (4 Stat Panels)

    • Devices Up / Total
    • Active Alarms (color-coded by severity)
    • Avg Cell Availability (%)
    • Current Peak Traffic (Gbps)
  2. Network Health (Time Series)

    • Cell Availability trend (7 days)
    • Alarm rate trend (7 days)
    • Traffic forecast vs. actual
  3. Recent Incidents (Table)

    • Time, Duration, Root Cause, Status
    • Last 7 days, sorted by severity
  4. Device Status Grid (Heatmap)

    • Rows: Devices, Columns: Health metrics
    • Green (OK) → Yellow (Degraded) → Red (Down)

Example Dashboard:

Grafana Overview Dashboard

Example showing base station overview with Last Reported, Connected UEs, Data Transferred, PRB Utilization, and Throughput metrics.

Dashboard 2: NOC Operations Dashboard

Audience: Network operations center team Refresh Rate: 10 seconds Purpose: Real-time operational control

Panels:

  1. Active Issues (Table)

    • Time, Severity, Device, Issue, Duration
    • Sort by severity, click to drill-down
  2. Resource Utilization (Gauges)

    • DL Resource % (per site)
    • UL Resource % (per site)
    • CPU % on devices
  3. Traffic Overview (Area Chart)

    • DL/UL throughput (last 24 hours)
    • Current vs. 24-hour average
    • Peak hour indicators
  4. Alarm Trend (Bar Chart)

    • Count by severity (last hour, rolling)
    • Stacked bar showing distribution
  5. Device Status (Quick View)

    • Device name, IP, Status (green/red)
    • Last metric update timestamp
    • Links to device-specific dashboard
  6. Recent Events (Time Series)

    • Alarms appearing/clearing
    • Configuration changes
    • Session status changes

Example Dashboard:

Grafana 4G Status and Alarms

Example showing 4G Status overview with geographic map, alarm table with severity levels, and performance statistics.

Dashboard 3: Engineering Deep-Dive

Audience: Network engineers Refresh Rate: 1 minute Purpose: Detailed technical analysis

Panels:

  1. Traffic Pattern Analysis (Multi-Series)

    • DL/UL by site for comparison
    • Baseline + current overlay
    • Hour-of-day seasonality
  2. Cell Quality Metrics (Multi-Series)

    • SINR distribution (histogram)
    • RSRP distribution (histogram)
    • Handover success rate trends
  3. Radio Performance (Time Series)

    • RLC retransmission rate (by site)
    • RRC setup success rate
    • Call drop rate
  4. Configuration Audit (Table)

    • Device, Config Date, Parameters Changed
    • Highlights recent modifications
  5. Correlation Analysis (Scatter)

    • DL Resource vs. Traffic
    • Traffic vs. Handover Success
    • Availability vs. Alarm Count

Dashboard 4: On-Call Alert Dashboard

Audience: Incident responders (on-call) Refresh Rate: 5 seconds Purpose: Rapid incident assessment and response

Panels:

  1. Alert Summary (Large Stat)

    • Count of active critical alerts
    • Background color: Green (OK) / Red (Issues)
  2. Critical Issues (Table, Big Text)

    • Device, Issue, Duration
    • Auto-scroll to newest first
  3. Related Metrics (Time Series)

    • Traffic, Resource utilization
    • Quality metrics for affected device
    • Auto-populate based on alert
  4. Recent Changes (Table)

    • Configuration changes in last 4 hours
    • Software versions
    • Parameter modifications
  5. Similar Issues (Table)

    • Same issue type in last 30 days
    • Time to resolution
    • Root causes identified
  6. Escalation Path (Text Panel)

    • Next level escalation contact
    • Maintenance window info
    • Related ticket/incident number

Dashboard 5: Nokia AirScale Detailed Performance

Audience: RF engineers, performance analysts Refresh Rate: 30 seconds Purpose: Deep-dive Nokia-specific metrics and KPIs

This dashboard uses Nokia-specific performance counters to provide comprehensive visibility into AirScale base station performance. See the Nokia Counter Reference for detailed counter definitions.

Panel 1: Resource Utilization Overview (Gauges)

Shows current PRB (Physical Resource Block) usage:

// Downlink PRB Usage
from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8011C37")
|> filter(fn: (r) => r._field == "counterValue")
|> group()
|> mean()
|> map(fn: (r) => ({ r with _value: r._value / 10.0})) // Convert to percentage
|> rename(columns: {_value: "DL PRB Usage"})

// Uplink PRB Usage
from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8011C24")
|> filter(fn: (r) => r._field == "counterValue")
|> group()
|> mean()
|> map(fn: (r) => ({ r with _value: r._value / 10.0})) // Convert to percentage
|> rename(columns: {_value: "UL PRB Usage"})

Visualization: Gauge panels with thresholds:

  • Green: 0-70%
  • Yellow: 70-85%
  • Red: 85-100%

Panel 2: Throughput Trends (Time Series)

Displays PDCP layer throughput for downlink and uplink:

// Downlink Throughput
from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8012C26")
|> filter(fn: (r) => r._field == "counterValue")
|> map(fn: (r) => ({ r with _value: r._value / 1000.0})) // Convert to Mbps
|> rename(columns: {_value: "Downlink Mbps"})

// Uplink Throughput
from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8012C23")
|> filter(fn: (r) => r._field == "counterValue")
|> map(fn: (r) => ({ r with _value: r._value / 1000.0})) // Convert to Mbps
|> rename(columns: {_value: "Uplink Mbps"})

Counters Used:

  • M8012C26 - PDCP Throughput DL Mean (kbit/s)
  • M8012C23 - PDCP Throughput UL Mean (kbit/s)

Panel 3: Active UE Count (Time Series)

Tracks the number of connected users:

from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8018C1")
|> filter(fn: (r) => r._field == "counterValue")
|> rename(columns: {_value: "UEs Connected"})

Counter Used:

  • M8018C1 - Active UE per eNB max (count)

Panel 4: Cell Availability (Time Series with Alert Threshold)

Calculates and displays cell availability percentage:

import "strings"

from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8020C3" or
r["metricCounter"] == "M8020C6" or
r["metricCounter"] == "M8020C4")
|> filter(fn: (r) => r._field == "counterValue")
|> filter(fn: (r) => strings.containsStr(v: r["DN"], substr: "${CellKey}"))
|> group()
|> pivot(rowKey:["_time"], columnKey: ["metricCounter"], valueColumn: "_value")
|> map(fn: (r) => ({
_time: r._time,
"Cell Availability": 100.0 * r.M8020C3 / (r.M8020C6 - r.M8020C4)
}))

Counters Used:

  • M8020C3 - Samples when the cell is available
  • M8020C6 - Cell availability denominator
  • M8020C4 - Samples when the cell is planned unavailable

Threshold Alert: Cell Availability < 99%

Panel 5: Per-Cell PRB Utilization (Multi-Series Time Series)

Shows resource usage broken down by individual cells:

import "strings"

// Uplink PRB per cell
from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8011C24")
|> filter(fn: (r) => r._field == "counterValue")
|> filter(fn: (r) => strings.containsStr(v: r["DN"], substr: "${CellKey}"))
|> group()
|> map(fn: (r) => ({ r with _value: r._value / 10.0}))
|> rename(columns: {"_value": "Average Uplink PRB Usage"})

// Downlink PRB per cell
from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8011C37")
|> filter(fn: (r) => r._field == "counterValue")
|> filter(fn: (r) => strings.containsStr(v: r["DN"], substr: "${CellKey}"))
|> group()
|> map(fn: (r) => ({ r with _value: r._value / 10.0 }))
|> rename(columns: {"_value": "Average Downlink PRB Usage"})

Panel 6: Per-Cell Throughput (Multi-Series Time Series)

PDCP throughput broken down by cell:

import "strings"

// Downlink PDCP Throughput per cell
from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8012C26")
|> filter(fn: (r) => r._field == "counterValue")
|> filter(fn: (r) => strings.containsStr(v: r["DN"], substr: "${CellKey}"))
|> group()
|> rename(columns: {"_value": "Downlink PDCP Throughput"})

// Uplink PDCP Throughput per cell
from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8012C23")
|> filter(fn: (r) => r._field == "counterValue")
|> group()
|> rename(columns: {"_value": "Uplink PDCP Throughput"})

Panel 7: RSSI (Received Signal Strength Indicator) (Multi-Series Time Series)

Displays uplink signal strength statistics:

import "strings"

// Minimum RSSI
from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8005C0")
|> filter(fn: (r) => r._field == "counterValue")
|> filter(fn: (r) => strings.containsStr(v: r["DN"], substr: "${CellKey}"))
|> group()
|> rename(columns: {"_value": "Minimum RSSI"})

// Mean RSSI
from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8005C2")
|> filter(fn: (r) => r._field == "counterValue")
|> filter(fn: (r) => strings.containsStr(v: r["DN"], substr: "${CellKey}"))
|> group()
|> rename(columns: {"_value": "Mean RSSI"})

// Maximum RSSI
from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8005C1")
|> filter(fn: (r) => r._field == "counterValue")
|> filter(fn: (r) => strings.containsStr(v: r["DN"], substr: "${CellKey}"))
|> group()
|> rename(columns: {"_value": "Maximum RSSI"})

Counters Used:

  • M8005C0 - RSSI for PUCCH Min (dBm)
  • M8005C1 - RSSI for PUCCH Max (dBm)
  • M8005C2 - RSSI for PUCCH Mean (dBm)

Panel 8: Latency (Time Series)

PDCP SDU delay measurement:

import "strings"

from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8001C2")
|> filter(fn: (r) => r._field == "counterValue")
|> filter(fn: (r) => strings.containsStr(v: r["DN"], substr: "${CellKey}"))
|> group()
|> rename(columns: {"_value": "Latency"})

Counter Used:

  • M8001C2 - PDCP SDU delay on DL DTCH Mean (ms)

Panel 9: RRC Connection Setup Success Rate (Time Series with Threshold)

Calculates the percentage of successful connection setups:

import "strings"

from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M8013C5" or
r["metricCounter"] == "M8013C17" or
r["metricCounter"] == "M8013C18" or
r["metricCounter"] == "M8013C19" or
r["metricCounter"] == "M8013C34" or
r["metricCounter"] == "M8013C31" or
r["metricCounter"] == "M8013C21" or
r["metricCounter"] == "M8013C93" or
r["metricCounter"] == "M8013C91")
|> filter(fn: (r) => r._field == "counterValue")
|> filter(fn: (r) => strings.containsStr(v: r["DN"], substr: "${CellKey}"))
|> group()
|> pivot(rowKey:["_time"], columnKey: ["metricCounter"], valueColumn: "_value")
|> map(fn: (r) => ({
_time: r._time,
"Setup Success Ratio": 100.0 * r.M8013C5 / (r.M8013C17 + r.M8013C18 + r.M8013C19 + r.M8013C34 + r.M8013C31 + r.M8013C21 + r.M8013C93 + r.M8013C91)
}))

Counters Used:

  • M8013C5 - Signaling Connection Establishment Completions
  • M8013C17-M8013C93 - Various connection attempt types

Threshold Alert: Setup Success Ratio < 95%

Panel 10: VSWR (Voltage Standing Wave Ratio) by Antenna (Time Series)

Hardware health monitoring for antenna systems:

import "strings"

from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M40001C0")
|> filter(fn: (r) => r._field == "counterValue")
|> filter(fn: (r) => strings.containsStr(v: r["DN"], substr: "${RadioKey}"))
|> map(fn: (r) => ({
r with
"DN": strings.split(v: r["DN"], t: "/")[5],
"VSWR": r._value / 10.0
}))
|> group()
|> pivot(rowKey: ["_time"], columnKey: ["DN"], valueColumn: "VSWR")

Counter Used:

  • M40001C0 - VSWR per antenna branch (0.1 ratio)

Threshold Alert: VSWR > 2.0

Panel 11: Power Consumption (Time Series)

Base station power usage monitoring:

from(bucket: "nokia-monitor")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["recordType"] == "performanceMetric")
|> filter(fn: (r) => r["basebandName"] == "${Airscale}")
|> filter(fn: (r) => r["metricCounter"] == "M40002C2")
|> filter(fn: (r) => r._field == "counterValue")
|> group()
|> map(fn: (r) => ({ r with _value: r._value / 100000.0 }))
|> rename(columns: {"_value": "Power Consumption"})

Counter Used:

  • M40002C2 - Power Consumption (factor of 100000)

Dashboard Variables:

This dashboard uses Grafana template variables for dynamic filtering:

  • ${Airscale} - Base station selector (dropdown)
  • ${CellKey} - Cell selector for multi-cell sites (dropdown)
  • ${RadioKey} - Radio unit selector for VSWR (dropdown)

Alert Rules for this Dashboard:

  1. High PRB Utilization - Triggers when DL or UL PRB > 85% for 5 minutes
  2. Low Cell Availability - Triggers when availability < 99% for 10 minutes
  3. Poor Setup Success Rate - Triggers when RRC setup success < 95% for 5 minutes
  4. High VSWR - Triggers when VSWR > 2.0 for any antenna for 15 minutes
  5. Abnormal Power Consumption - Triggers when power deviates > 20% from baseline

Using this Dashboard:

  1. Capacity Analysis - Monitor PRB utilization to identify cells approaching capacity
  2. Performance Troubleshooting - Use RSSI, latency, and setup success rate to diagnose issues
  3. Hardware Health - Track VSWR and power consumption for proactive maintenance
  4. Quality Assurance - Monitor availability and throughput for SLA compliance

See Nokia Counter Reference for complete counter definitions and usage guidelines.

Example Dashboard - Detailed View:

Grafana Detailed Dashboard

Nokia Monitor detailed dashboard showing S1 Connections, operational state, data transferred, UEs connected, average PRB usage, performance monitoring metrics, and geographic operation map.

Example Dashboard - Cell Availability, PRB Usage, and Throughput:

Grafana Cell Availability and Metrics

Cell Availability charts per LNCEL, LTE PRB Usage per TTI for uplink/downlink, and PDCP Throughput charts showing performance across multiple cells.

Example Dashboard - RSSI, Power, Latency, and RRC:

Grafana RSSI and Performance Metrics

RSSI charts (Min/Mean/Max), Combined Power Consumption, Latency measurements, RRC Setup Success Ratio, and VSWR (RMOD) charts for multiple cells.

Example Dashboard - Additional Performance Panels:

Grafana Additional Metrics

Additional performance panels showing RRC Setup Success Ratio continuation, Latency measurements, VSWR RMOD charts, and UEs Connected over time.

Example Dashboard - VSWR Detail with Tooltip:

Grafana VSWR Detail

Detailed VSWR RMOD-2 chart showing antenna measurements (ANTL-1, ANTL-2, ANTL-3, ANTL-4) with interactive tooltip displaying timestamp and values.


Troubleshooting

No Data Appearing in Panels

Symptoms:

  • Dashboard loads but panels show "No data"
  • Data source appears connected

Diagnosis:

1. Check InfluxDB query is valid
2. Verify measurement name exists in InfluxDB
3. Check time range includes data points
4. Verify filter conditions match data tags

Solution:

  • Test query in InfluxDB UI directly
  • Adjust time range (try last 24 hours)
  • Check tag names match RAN Monitor output
  • Enable query inspector to see actual results

Slow Dashboard Loading

Symptoms:

  • Dashboard takes > 5 seconds to load
  • Panels appear one by one slowly

Diagnosis:

1. Too many panels (> 8)
2. Queries are too complex/large data range
3. InfluxDB performance issues
4. Network latency

Solution:

  • Reduce number of panels
  • Limit time range (24h vs. 1 year)
  • Pre-aggregate data in InfluxDB
  • Check InfluxDB CPU/memory
  • Increase query timeout

Alerts Not Firing

Symptoms:

  • Alert rule is enabled
  • Condition should be met
  • No notifications received

Diagnosis:

1. Check alert evaluation is happening
2. Verify notification channel is working
3. Check alert rule condition
4. Review notification channel logs

Solution:

  • Test alert rule manually (pencil icon → Test)
  • Check alert rule status in Alerting → Alert rules
  • Verify notification channel has correct URL/key
  • Check Grafana logs for errors
  • Re-test notification channel with manual trigger

Incorrect Data in Dashboards

Symptoms:

  • Values don't match expected numbers
  • Data looks shifted in time
  • Aggregations seem wrong

Diagnosis:

1. Check time zone settings
2. Verify aggregation function
3. Check tag/label filters
4. Review query for math errors

Solution:

  • Set dashboard time zone to match InfluxDB
  • Verify aggregateWindow function (mean/sum/last)
  • Test filters in InfluxDB directly
  • Simplify query to isolate issue

Data Retention Issues

Symptoms:

  • Historical data missing or incomplete
  • Queries return less data than expected
  • Dashboard shows gaps in time series

Solution:

  • Check retention policy settings in Data Retention Policy
  • Verify retention period is sufficient for your query time range
  • Adjust per-eNodeB retention if needed

Configuration Issues

Symptoms:

  • InfluxDB connection errors
  • Missing eNodeB data in queries
  • Incorrect data collection intervals

Solution: