[{"data":1,"prerenderedAt":1752},["ShallowReactive",2],{"post-\u002Fblog\u002Fvoip-monitoring-prometheus-grafana":3},{"id":4,"title":5,"author":6,"body":7,"category":1734,"coverImage":1735,"date":1736,"description":1737,"extension":1738,"meta":1739,"navigation":301,"path":1740,"readingTime":326,"seo":1741,"stem":1742,"tags":1743,"__hash__":1751},"posts\u002Fblog\u002Fvoip-monitoring-prometheus-grafana.md","VoIP Monitoring with Prometheus and Grafana","Tumarm Engineering",{"type":8,"value":9,"toc":1721},"minimark",[10,14,18,23,26,93,96,100,112,115,217,220,228,231,384,388,391,396,499,536,540,543,734,738,745,770,773,855,858,862,1339,1343,1346,1352,1368,1373,1387,1392,1403,1408,1422,1426,1684,1688,1691,1714,1717],[11,12,5],"h1",{"id":13},"voip-monitoring-with-prometheus-and-grafana",[15,16,17],"p",{},"VoIP systems fail in ways that don't show up in generic infrastructure monitoring. CPU at 20%, memory healthy, network up — but calls are dropping because the SIP registrar is rejecting REGISTERs due to a certificate expiry, or RTP packet loss is hitting 8% on a specific carrier route, or the T38 fax relay is silently failing. This post covers building a VoIP-specific observability stack that surfaces the metrics that matter: call quality, registration health, SIP error rates, and carrier route performance.",[19,20,22],"h2",{"id":21},"what-to-monitor-in-voip-infrastructure","What to Monitor in VoIP Infrastructure",[15,24,25],{},"Before instrumenting anything, define the four signal types for VoIP:",[27,28,29,45],"table",{},[30,31,32],"thead",{},[33,34,35,39,42],"tr",{},[36,37,38],"th",{},"Signal",[36,40,41],{},"Examples",[36,43,44],{},"Tool",[46,47,48,60,71,82],"tbody",{},[33,49,50,54,57],{},[51,52,53],"td",{},"SIP signaling",[51,55,56],{},"INVITE rate, 4xx\u002F5xx rate, REGISTER failures",[51,58,59],{},"kamailio_exporter, snmp_exporter",[33,61,62,65,68],{},[51,63,64],{},"Media quality",[51,66,67],{},"Packet loss, jitter, MOS score",[51,69,70],{},"rtpengine metrics, Homer SIPcapture",[33,72,73,76,79],{},[51,74,75],{},"Infrastructure",[51,77,78],{},"CPU, memory, network I\u002FO, disk",[51,80,81],{},"node_exporter",[33,83,84,87,90],{},[51,85,86],{},"Business",[51,88,89],{},"ASR (answer rate), ACD (avg call duration), NER",[51,91,92],{},"CDR database queries",[15,94,95],{},"A complete monitoring setup scrapes all four. Infrastructure metrics alone give you uptime; the other three give you quality.",[19,97,99],{"id":98},"kamailio-metrics-with-kamailio_exporter","Kamailio Metrics with kamailio_exporter",[15,101,102,103,107,108,111],{},"Kamailio exposes internal statistics via the ",[104,105,106],"code",{},"statistics"," module. The ",[104,109,110],{},"kamailio_exporter"," translates these to Prometheus metrics.",[15,113,114],{},"Install the exporter:",[116,117,122],"pre",{"className":118,"code":119,"language":120,"meta":121,"style":121},"language-bash shiki shiki-themes github-light github-dark","# Run kamailio_exporter as a sidecar\ndocker run -d \\\n  --name kamailio-exporter \\\n  --network host \\\n  -e KAMAILIO_HOST=127.0.0.1 \\\n  -e KAMAILIO_PORT=5060 \\\n  -p 9494:9494 \\\n  hunterlong\u002Fkamailio-exporter\n","bash","",[104,123,124,133,151,162,173,187,200,211],{"__ignoreMap":121},[125,126,129],"span",{"class":127,"line":128},"line",1,[125,130,132],{"class":131},"sJ8bj","# Run kamailio_exporter as a sidecar\n",[125,134,136,140,144,148],{"class":127,"line":135},2,[125,137,139],{"class":138},"sScJk","docker",[125,141,143],{"class":142},"sZZnC"," run",[125,145,147],{"class":146},"sj4cs"," -d",[125,149,150],{"class":146}," \\\n",[125,152,154,157,160],{"class":127,"line":153},3,[125,155,156],{"class":146},"  --name",[125,158,159],{"class":142}," kamailio-exporter",[125,161,150],{"class":146},[125,163,165,168,171],{"class":127,"line":164},4,[125,166,167],{"class":146},"  --network",[125,169,170],{"class":142}," host",[125,172,150],{"class":146},[125,174,176,179,182,185],{"class":127,"line":175},5,[125,177,178],{"class":146},"  -e",[125,180,181],{"class":142}," KAMAILIO_HOST=",[125,183,184],{"class":146},"127.0.0.1",[125,186,150],{"class":146},[125,188,190,192,195,198],{"class":127,"line":189},6,[125,191,178],{"class":146},[125,193,194],{"class":142}," KAMAILIO_PORT=",[125,196,197],{"class":146},"5060",[125,199,150],{"class":146},[125,201,203,206,209],{"class":127,"line":202},7,[125,204,205],{"class":146},"  -p",[125,207,208],{"class":142}," 9494:9494",[125,210,150],{"class":146},[125,212,214],{"class":127,"line":213},8,[125,215,216],{"class":142},"  hunterlong\u002Fkamailio-exporter\n",[15,218,219],{},"Or configure Kamailio to expose a JSON stats endpoint:",[116,221,226],{"className":222,"code":224,"language":225},[223],"language-text","loadmodule \"xhttp.so\"\nloadmodule \"statistics.so\"\n\nevent_route[xhttp:request] {\n    if ($hu =~ \"^\u002Fmetrics\") {\n        xhttp_reply(\"200\", \"OK\", \"text\u002Fplain\", $stat(all));\n        exit;\n    }\n}\n","text",[104,227,224],{"__ignoreMap":121},[15,229,230],{},"Key Kamailio metrics to track:",[116,232,236],{"className":233,"code":234,"language":235,"meta":121,"style":121},"language-yaml shiki shiki-themes github-light github-dark","# prometheus\u002Frecording_rules.yml\ngroups:\n  - name: kamailio_derived\n    rules:\n      - record: kamailio:sip_4xx_rate\n        expr: rate(kamailio_core_rcv_requests_total{method=\"INVITE\",status=~\"4..\"}[5m])\n\n      - record: kamailio:register_failure_rate\n        expr: rate(kamailio_core_rcv_requests_total{method=\"REGISTER\",status=\"401\"}[5m])\n           \u002F rate(kamailio_core_rcv_requests_total{method=\"REGISTER\"}[5m])\n\n      - record: kamailio:active_dialogs\n        expr: kamailio_dialog_active\n\n      - record: kamailio:invite_per_second\n        expr: rate(kamailio_core_rcv_requests_total{method=\"INVITE\"}[1m])\n","yaml",[104,237,238,243,253,267,274,287,297,303,314,324,330,335,347,357,362,374],{"__ignoreMap":121},[125,239,240],{"class":127,"line":128},[125,241,242],{"class":131},"# prometheus\u002Frecording_rules.yml\n",[125,244,245,249],{"class":127,"line":135},[125,246,248],{"class":247},"s9eBZ","groups",[125,250,252],{"class":251},"sVt8B",":\n",[125,254,255,258,261,264],{"class":127,"line":153},[125,256,257],{"class":251},"  - ",[125,259,260],{"class":247},"name",[125,262,263],{"class":251},": ",[125,265,266],{"class":142},"kamailio_derived\n",[125,268,269,272],{"class":127,"line":164},[125,270,271],{"class":247},"    rules",[125,273,252],{"class":251},[125,275,276,279,282,284],{"class":127,"line":175},[125,277,278],{"class":251},"      - ",[125,280,281],{"class":247},"record",[125,283,263],{"class":251},[125,285,286],{"class":142},"kamailio:sip_4xx_rate\n",[125,288,289,292,294],{"class":127,"line":189},[125,290,291],{"class":247},"        expr",[125,293,263],{"class":251},[125,295,296],{"class":142},"rate(kamailio_core_rcv_requests_total{method=\"INVITE\",status=~\"4..\"}[5m])\n",[125,298,299],{"class":127,"line":202},[125,300,302],{"emptyLinePlaceholder":301},true,"\n",[125,304,305,307,309,311],{"class":127,"line":213},[125,306,278],{"class":251},[125,308,281],{"class":247},[125,310,263],{"class":251},[125,312,313],{"class":142},"kamailio:register_failure_rate\n",[125,315,317,319,321],{"class":127,"line":316},9,[125,318,291],{"class":247},[125,320,263],{"class":251},[125,322,323],{"class":142},"rate(kamailio_core_rcv_requests_total{method=\"REGISTER\",status=\"401\"}[5m])\n",[125,325,327],{"class":127,"line":326},10,[125,328,329],{"class":142},"           \u002F rate(kamailio_core_rcv_requests_total{method=\"REGISTER\"}[5m])\n",[125,331,333],{"class":127,"line":332},11,[125,334,302],{"emptyLinePlaceholder":301},[125,336,338,340,342,344],{"class":127,"line":337},12,[125,339,278],{"class":251},[125,341,281],{"class":247},[125,343,263],{"class":251},[125,345,346],{"class":142},"kamailio:active_dialogs\n",[125,348,350,352,354],{"class":127,"line":349},13,[125,351,291],{"class":247},[125,353,263],{"class":251},[125,355,356],{"class":142},"kamailio_dialog_active\n",[125,358,360],{"class":127,"line":359},14,[125,361,302],{"emptyLinePlaceholder":301},[125,363,365,367,369,371],{"class":127,"line":364},15,[125,366,278],{"class":251},[125,368,281],{"class":247},[125,370,263],{"class":251},[125,372,373],{"class":142},"kamailio:invite_per_second\n",[125,375,377,379,381],{"class":127,"line":376},16,[125,378,291],{"class":247},[125,380,263],{"class":251},[125,382,383],{"class":142},"rate(kamailio_core_rcv_requests_total{method=\"INVITE\"}[1m])\n",[19,385,387],{"id":386},"asterisk-metrics","Asterisk Metrics",[15,389,390],{},"Asterisk does not natively expose Prometheus metrics. Use one of two approaches:",[392,393,395],"h3",{"id":394},"option-1-asterisk_exporter-ami-based","Option 1: asterisk_exporter (AMI-based)",[116,397,399],{"className":233,"code":398,"language":235,"meta":121,"style":121},"# \u002Fetc\u002Fasterisk_exporter\u002Fconfig.yml\nami:\n  host: 127.0.0.1\n  port: 5038\n  username: prometheus\n  password: secret\n\nmetrics:\n  - active_channels\n  - active_calls\n  - active_agents\n  - queue_waiting\n  - queue_completed\n",[104,400,401,406,413,423,433,443,453,457,464,471,478,485,492],{"__ignoreMap":121},[125,402,403],{"class":127,"line":128},[125,404,405],{"class":131},"# \u002Fetc\u002Fasterisk_exporter\u002Fconfig.yml\n",[125,407,408,411],{"class":127,"line":135},[125,409,410],{"class":247},"ami",[125,412,252],{"class":251},[125,414,415,418,420],{"class":127,"line":153},[125,416,417],{"class":247},"  host",[125,419,263],{"class":251},[125,421,422],{"class":146},"127.0.0.1\n",[125,424,425,428,430],{"class":127,"line":164},[125,426,427],{"class":247},"  port",[125,429,263],{"class":251},[125,431,432],{"class":146},"5038\n",[125,434,435,438,440],{"class":127,"line":175},[125,436,437],{"class":247},"  username",[125,439,263],{"class":251},[125,441,442],{"class":142},"prometheus\n",[125,444,445,448,450],{"class":127,"line":189},[125,446,447],{"class":247},"  password",[125,449,263],{"class":251},[125,451,452],{"class":142},"secret\n",[125,454,455],{"class":127,"line":202},[125,456,302],{"emptyLinePlaceholder":301},[125,458,459,462],{"class":127,"line":213},[125,460,461],{"class":247},"metrics",[125,463,252],{"class":251},[125,465,466,468],{"class":127,"line":316},[125,467,257],{"class":251},[125,469,470],{"class":142},"active_channels\n",[125,472,473,475],{"class":127,"line":326},[125,474,257],{"class":251},[125,476,477],{"class":142},"active_calls\n",[125,479,480,482],{"class":127,"line":332},[125,481,257],{"class":251},[125,483,484],{"class":142},"active_agents\n",[125,486,487,489],{"class":127,"line":337},[125,488,257],{"class":251},[125,490,491],{"class":142},"queue_waiting\n",[125,493,494,496],{"class":127,"line":349},[125,495,257],{"class":251},[125,497,498],{"class":142},"queue_completed\n",[116,500,504],{"className":501,"code":502,"language":503,"meta":121,"style":121},"language-ini shiki shiki-themes github-light github-dark","# \u002Fetc\u002Fasterisk\u002Fmanager.conf\n[prometheus]\nsecret=secret\npermit=127.0.0.1\u002F255.255.255.255\nread=system,call,agent,user,config,dtmf,reporting,cdr,dialplan\nwrite=\n","ini",[104,505,506,511,516,521,526,531],{"__ignoreMap":121},[125,507,508],{"class":127,"line":128},[125,509,510],{},"# \u002Fetc\u002Fasterisk\u002Fmanager.conf\n",[125,512,513],{"class":127,"line":135},[125,514,515],{},"[prometheus]\n",[125,517,518],{"class":127,"line":153},[125,519,520],{},"secret=secret\n",[125,522,523],{"class":127,"line":164},[125,524,525],{},"permit=127.0.0.1\u002F255.255.255.255\n",[125,527,528],{"class":127,"line":175},[125,529,530],{},"read=system,call,agent,user,config,dtmf,reporting,cdr,dialplan\n",[125,532,533],{"class":127,"line":189},[125,534,535],{},"write=\n",[392,537,539],{"id":538},"option-2-cel-to-prometheus-via-lokigrafana-pipeline","Option 2: CEL to Prometheus via Loki\u002FGrafana pipeline",[15,541,542],{},"Write CDR\u002FCEL events to a PostgreSQL table and expose them via a custom exporter. This approach gives you business metrics (ASR, ACD, call volumes by trunk) that the AMI exporter cannot provide:",[116,544,548],{"className":545,"code":546,"language":547,"meta":121,"style":121},"language-python shiki shiki-themes github-light github-dark","# asterisk_business_exporter.py\nfrom prometheus_client import Gauge, start_http_server\nimport psycopg2\nimport time\n\nasr_gauge = Gauge('asterisk_asr_ratio', 'Answer-Seizure Ratio', ['trunk'])\nacd_gauge = Gauge('asterisk_acd_seconds', 'Average Call Duration', ['trunk'])\ncalls_gauge = Gauge('asterisk_active_calls', 'Active calls', ['direction'])\n\ndef collect_metrics():\n    conn = psycopg2.connect(\"host=localhost dbname=asterisk_cdr user=monitor\")\n    cur = conn.cursor()\n    \n    # ASR per trunk (last 5 minutes)\n    cur.execute(\"\"\"\n        SELECT\n            accountcode AS trunk,\n            ROUND(AVG(CASE WHEN disposition='ANSWERED' THEN 1.0 ELSE 0.0 END), 3) AS asr,\n            AVG(CASE WHEN disposition='ANSWERED' THEN billsec ELSE NULL END) AS acd\n        FROM cdr\n        WHERE calldate > NOW() - INTERVAL '5 minutes'\n          AND accountcode IS NOT NULL\n        GROUP BY accountcode\n    \"\"\")\n    \n    for trunk, asr, acd in cur.fetchall():\n        asr_gauge.labels(trunk=trunk).set(asr or 0)\n        acd_gauge.labels(trunk=trunk).set(acd or 0)\n\nif __name__ == '__main__':\n    start_http_server(9200)\n    while True:\n        collect_metrics()\n        time.sleep(30)\n","python",[104,549,550,555,560,565,570,574,579,584,589,593,598,603,608,613,618,623,628,634,640,646,652,658,664,670,676,681,687,693,699,704,710,716,722,728],{"__ignoreMap":121},[125,551,552],{"class":127,"line":128},[125,553,554],{},"# asterisk_business_exporter.py\n",[125,556,557],{"class":127,"line":135},[125,558,559],{},"from prometheus_client import Gauge, start_http_server\n",[125,561,562],{"class":127,"line":153},[125,563,564],{},"import psycopg2\n",[125,566,567],{"class":127,"line":164},[125,568,569],{},"import time\n",[125,571,572],{"class":127,"line":175},[125,573,302],{"emptyLinePlaceholder":301},[125,575,576],{"class":127,"line":189},[125,577,578],{},"asr_gauge = Gauge('asterisk_asr_ratio', 'Answer-Seizure Ratio', ['trunk'])\n",[125,580,581],{"class":127,"line":202},[125,582,583],{},"acd_gauge = Gauge('asterisk_acd_seconds', 'Average Call Duration', ['trunk'])\n",[125,585,586],{"class":127,"line":213},[125,587,588],{},"calls_gauge = Gauge('asterisk_active_calls', 'Active calls', ['direction'])\n",[125,590,591],{"class":127,"line":316},[125,592,302],{"emptyLinePlaceholder":301},[125,594,595],{"class":127,"line":326},[125,596,597],{},"def collect_metrics():\n",[125,599,600],{"class":127,"line":332},[125,601,602],{},"    conn = psycopg2.connect(\"host=localhost dbname=asterisk_cdr user=monitor\")\n",[125,604,605],{"class":127,"line":337},[125,606,607],{},"    cur = conn.cursor()\n",[125,609,610],{"class":127,"line":349},[125,611,612],{},"    \n",[125,614,615],{"class":127,"line":359},[125,616,617],{},"    # ASR per trunk (last 5 minutes)\n",[125,619,620],{"class":127,"line":364},[125,621,622],{},"    cur.execute(\"\"\"\n",[125,624,625],{"class":127,"line":376},[125,626,627],{},"        SELECT\n",[125,629,631],{"class":127,"line":630},17,[125,632,633],{},"            accountcode AS trunk,\n",[125,635,637],{"class":127,"line":636},18,[125,638,639],{},"            ROUND(AVG(CASE WHEN disposition='ANSWERED' THEN 1.0 ELSE 0.0 END), 3) AS asr,\n",[125,641,643],{"class":127,"line":642},19,[125,644,645],{},"            AVG(CASE WHEN disposition='ANSWERED' THEN billsec ELSE NULL END) AS acd\n",[125,647,649],{"class":127,"line":648},20,[125,650,651],{},"        FROM cdr\n",[125,653,655],{"class":127,"line":654},21,[125,656,657],{},"        WHERE calldate > NOW() - INTERVAL '5 minutes'\n",[125,659,661],{"class":127,"line":660},22,[125,662,663],{},"          AND accountcode IS NOT NULL\n",[125,665,667],{"class":127,"line":666},23,[125,668,669],{},"        GROUP BY accountcode\n",[125,671,673],{"class":127,"line":672},24,[125,674,675],{},"    \"\"\")\n",[125,677,679],{"class":127,"line":678},25,[125,680,612],{},[125,682,684],{"class":127,"line":683},26,[125,685,686],{},"    for trunk, asr, acd in cur.fetchall():\n",[125,688,690],{"class":127,"line":689},27,[125,691,692],{},"        asr_gauge.labels(trunk=trunk).set(asr or 0)\n",[125,694,696],{"class":127,"line":695},28,[125,697,698],{},"        acd_gauge.labels(trunk=trunk).set(acd or 0)\n",[125,700,702],{"class":127,"line":701},29,[125,703,302],{"emptyLinePlaceholder":301},[125,705,707],{"class":127,"line":706},30,[125,708,709],{},"if __name__ == '__main__':\n",[125,711,713],{"class":127,"line":712},31,[125,714,715],{},"    start_http_server(9200)\n",[125,717,719],{"class":127,"line":718},32,[125,720,721],{},"    while True:\n",[125,723,725],{"class":127,"line":724},33,[125,726,727],{},"        collect_metrics()\n",[125,729,731],{"class":127,"line":730},34,[125,732,733],{},"        time.sleep(30)\n",[19,735,737],{"id":736},"rtpengine-metrics","rtpengine Metrics",[15,739,740,741,744],{},"rtpengine exposes Prometheus metrics natively when built with ",[104,742,743],{},"--with-transcoding",":",[116,746,748],{"className":501,"code":747,"language":503,"meta":121,"style":121},"# \u002Fetc\u002Frtpengine\u002Frtpengine.conf\n[rtpengine]\nprometheus = yes\nprometheus-listen = 127.0.0.1:9900\n",[104,749,750,755,760,765],{"__ignoreMap":121},[125,751,752],{"class":127,"line":128},[125,753,754],{},"# \u002Fetc\u002Frtpengine\u002Frtpengine.conf\n",[125,756,757],{"class":127,"line":135},[125,758,759],{},"[rtpengine]\n",[125,761,762],{"class":127,"line":153},[125,763,764],{},"prometheus = yes\n",[125,766,767],{"class":127,"line":164},[125,768,769],{},"prometheus-listen = 127.0.0.1:9900\n",[15,771,772],{},"Key media quality metrics from rtpengine:",[27,774,775,788],{},[30,776,777],{},[33,778,779,782,785],{},[36,780,781],{},"Metric",[36,783,784],{},"Alert threshold",[36,786,787],{},"Description",[46,789,790,803,816,829,842],{},[33,791,792,797,800],{},[51,793,794],{},[104,795,796],{},"rtpengine_packet_loss_ratio",[51,798,799],{},"> 0.03",[51,801,802],{},"Packet loss > 3%",[33,804,805,810,813],{},[51,806,807],{},[104,808,809],{},"rtpengine_jitter_ms",[51,811,812],{},"> 50",[51,814,815],{},"Jitter > 50ms",[33,817,818,823,826],{},[51,819,820],{},[104,821,822],{},"rtpengine_mos_score",[51,824,825],{},"\u003C 3.5",[51,827,828],{},"MOS below acceptable",[33,830,831,836,839],{},[51,832,833],{},[104,834,835],{},"rtpengine_active_sessions",[51,837,838],{},"> 80% capacity",[51,840,841],{},"Approaching session limit",[33,843,844,849,852],{},[51,845,846],{},[104,847,848],{},"rtpengine_transcoded_sessions",[51,850,851],{},"Rate spike",[51,853,854],{},"Unexpected transcoding",[15,856,857],{},"MOS (Mean Opinion Score) ranges from 1 (unusable) to 5 (excellent). A score above 4.0 is toll-quality; 3.5–4.0 is acceptable; below 3.5 users notice degradation. Set your alert at 3.5.",[19,859,861],{"id":860},"prometheus-alerting-rules","Prometheus Alerting Rules",[116,863,865],{"className":233,"code":864,"language":235,"meta":121,"style":121},"# prometheus\u002Falerts\u002Fvoip.yml\ngroups:\n  - name: voip_sip\n    rules:\n      - alert: HighSIP5xxRate\n        expr: |\n          rate(kamailio_core_rcv_replies_total{status=~\"5..\"}[5m])\n          \u002F rate(kamailio_core_rcv_replies_total[5m]) > 0.05\n        for: 3m\n        labels:\n          severity: critical\n          team: voip\n        annotations:\n          summary: \"SIP 5xx rate {{ $value | humanizePercentage }} on {{ $labels.instance }}\"\n          runbook: \"https:\u002F\u002Fwiki.example.com\u002Frunbooks\u002Fsip-5xx\"\n\n      - alert: KamailioDialogsHigh\n        expr: kamailio:active_dialogs > 8000\n        for: 5m\n        labels:\n          severity: warning\n        annotations:\n          summary: \"Active dialogs approaching capacity: {{ $value }}\"\n\n      - alert: RegistrationFailureSpike\n        expr: kamailio:register_failure_rate > 0.2\n        for: 2m\n        labels:\n          severity: critical\n        annotations:\n          summary: \"20%+ of SIP registrations failing — possible auth issue or attack\"\n\n  - name: voip_media\n    rules:\n      - alert: MediaQualityDegraded\n        expr: rtpengine_mos_score \u003C 3.5\n        for: 5m\n        labels:\n          severity: warning\n        annotations:\n          summary: \"MOS score {{ $value }} below 3.5 on {{ $labels.instance }}\"\n\n      - alert: MediaPacketLossHigh\n        expr: rtpengine_packet_loss_ratio > 0.03\n        for: 3m\n        labels:\n          severity: critical\n        annotations:\n          summary: \"RTP packet loss {{ $value | humanizePercentage }} — calls impacted\"\n\n      - alert: rtpengineCapacityHigh\n        expr: rtpengine_active_sessions \u002F rtpengine_max_sessions > 0.85\n        for: 5m\n        labels:\n          severity: warning\n        annotations:\n          summary: \"rtpengine at {{ $value | humanizePercentage }} capacity\"\n",[104,866,867,872,878,889,895,907,917,922,927,937,944,954,964,971,981,991,995,1006,1015,1024,1030,1039,1045,1054,1058,1069,1078,1087,1093,1101,1107,1116,1120,1131,1137,1149,1159,1168,1175,1184,1191,1201,1206,1218,1228,1237,1244,1253,1260,1270,1275,1287,1297,1306,1313,1322,1329],{"__ignoreMap":121},[125,868,869],{"class":127,"line":128},[125,870,871],{"class":131},"# prometheus\u002Falerts\u002Fvoip.yml\n",[125,873,874,876],{"class":127,"line":135},[125,875,248],{"class":247},[125,877,252],{"class":251},[125,879,880,882,884,886],{"class":127,"line":153},[125,881,257],{"class":251},[125,883,260],{"class":247},[125,885,263],{"class":251},[125,887,888],{"class":142},"voip_sip\n",[125,890,891,893],{"class":127,"line":164},[125,892,271],{"class":247},[125,894,252],{"class":251},[125,896,897,899,902,904],{"class":127,"line":175},[125,898,278],{"class":251},[125,900,901],{"class":247},"alert",[125,903,263],{"class":251},[125,905,906],{"class":142},"HighSIP5xxRate\n",[125,908,909,911,913],{"class":127,"line":189},[125,910,291],{"class":247},[125,912,263],{"class":251},[125,914,916],{"class":915},"szBVR","|\n",[125,918,919],{"class":127,"line":202},[125,920,921],{"class":142},"          rate(kamailio_core_rcv_replies_total{status=~\"5..\"}[5m])\n",[125,923,924],{"class":127,"line":213},[125,925,926],{"class":142},"          \u002F rate(kamailio_core_rcv_replies_total[5m]) > 0.05\n",[125,928,929,932,934],{"class":127,"line":316},[125,930,931],{"class":247},"        for",[125,933,263],{"class":251},[125,935,936],{"class":142},"3m\n",[125,938,939,942],{"class":127,"line":326},[125,940,941],{"class":247},"        labels",[125,943,252],{"class":251},[125,945,946,949,951],{"class":127,"line":332},[125,947,948],{"class":247},"          severity",[125,950,263],{"class":251},[125,952,953],{"class":142},"critical\n",[125,955,956,959,961],{"class":127,"line":337},[125,957,958],{"class":247},"          team",[125,960,263],{"class":251},[125,962,963],{"class":142},"voip\n",[125,965,966,969],{"class":127,"line":349},[125,967,968],{"class":247},"        annotations",[125,970,252],{"class":251},[125,972,973,976,978],{"class":127,"line":359},[125,974,975],{"class":247},"          summary",[125,977,263],{"class":251},[125,979,980],{"class":142},"\"SIP 5xx rate {{ $value | humanizePercentage }} on {{ $labels.instance }}\"\n",[125,982,983,986,988],{"class":127,"line":364},[125,984,985],{"class":247},"          runbook",[125,987,263],{"class":251},[125,989,990],{"class":142},"\"https:\u002F\u002Fwiki.example.com\u002Frunbooks\u002Fsip-5xx\"\n",[125,992,993],{"class":127,"line":376},[125,994,302],{"emptyLinePlaceholder":301},[125,996,997,999,1001,1003],{"class":127,"line":630},[125,998,278],{"class":251},[125,1000,901],{"class":247},[125,1002,263],{"class":251},[125,1004,1005],{"class":142},"KamailioDialogsHigh\n",[125,1007,1008,1010,1012],{"class":127,"line":636},[125,1009,291],{"class":247},[125,1011,263],{"class":251},[125,1013,1014],{"class":142},"kamailio:active_dialogs > 8000\n",[125,1016,1017,1019,1021],{"class":127,"line":642},[125,1018,931],{"class":247},[125,1020,263],{"class":251},[125,1022,1023],{"class":142},"5m\n",[125,1025,1026,1028],{"class":127,"line":648},[125,1027,941],{"class":247},[125,1029,252],{"class":251},[125,1031,1032,1034,1036],{"class":127,"line":654},[125,1033,948],{"class":247},[125,1035,263],{"class":251},[125,1037,1038],{"class":142},"warning\n",[125,1040,1041,1043],{"class":127,"line":660},[125,1042,968],{"class":247},[125,1044,252],{"class":251},[125,1046,1047,1049,1051],{"class":127,"line":666},[125,1048,975],{"class":247},[125,1050,263],{"class":251},[125,1052,1053],{"class":142},"\"Active dialogs approaching capacity: {{ $value }}\"\n",[125,1055,1056],{"class":127,"line":672},[125,1057,302],{"emptyLinePlaceholder":301},[125,1059,1060,1062,1064,1066],{"class":127,"line":678},[125,1061,278],{"class":251},[125,1063,901],{"class":247},[125,1065,263],{"class":251},[125,1067,1068],{"class":142},"RegistrationFailureSpike\n",[125,1070,1071,1073,1075],{"class":127,"line":683},[125,1072,291],{"class":247},[125,1074,263],{"class":251},[125,1076,1077],{"class":142},"kamailio:register_failure_rate > 0.2\n",[125,1079,1080,1082,1084],{"class":127,"line":689},[125,1081,931],{"class":247},[125,1083,263],{"class":251},[125,1085,1086],{"class":142},"2m\n",[125,1088,1089,1091],{"class":127,"line":695},[125,1090,941],{"class":247},[125,1092,252],{"class":251},[125,1094,1095,1097,1099],{"class":127,"line":701},[125,1096,948],{"class":247},[125,1098,263],{"class":251},[125,1100,953],{"class":142},[125,1102,1103,1105],{"class":127,"line":706},[125,1104,968],{"class":247},[125,1106,252],{"class":251},[125,1108,1109,1111,1113],{"class":127,"line":712},[125,1110,975],{"class":247},[125,1112,263],{"class":251},[125,1114,1115],{"class":142},"\"20%+ of SIP registrations failing — possible auth issue or attack\"\n",[125,1117,1118],{"class":127,"line":718},[125,1119,302],{"emptyLinePlaceholder":301},[125,1121,1122,1124,1126,1128],{"class":127,"line":724},[125,1123,257],{"class":251},[125,1125,260],{"class":247},[125,1127,263],{"class":251},[125,1129,1130],{"class":142},"voip_media\n",[125,1132,1133,1135],{"class":127,"line":730},[125,1134,271],{"class":247},[125,1136,252],{"class":251},[125,1138,1140,1142,1144,1146],{"class":127,"line":1139},35,[125,1141,278],{"class":251},[125,1143,901],{"class":247},[125,1145,263],{"class":251},[125,1147,1148],{"class":142},"MediaQualityDegraded\n",[125,1150,1152,1154,1156],{"class":127,"line":1151},36,[125,1153,291],{"class":247},[125,1155,263],{"class":251},[125,1157,1158],{"class":142},"rtpengine_mos_score \u003C 3.5\n",[125,1160,1162,1164,1166],{"class":127,"line":1161},37,[125,1163,931],{"class":247},[125,1165,263],{"class":251},[125,1167,1023],{"class":142},[125,1169,1171,1173],{"class":127,"line":1170},38,[125,1172,941],{"class":247},[125,1174,252],{"class":251},[125,1176,1178,1180,1182],{"class":127,"line":1177},39,[125,1179,948],{"class":247},[125,1181,263],{"class":251},[125,1183,1038],{"class":142},[125,1185,1187,1189],{"class":127,"line":1186},40,[125,1188,968],{"class":247},[125,1190,252],{"class":251},[125,1192,1194,1196,1198],{"class":127,"line":1193},41,[125,1195,975],{"class":247},[125,1197,263],{"class":251},[125,1199,1200],{"class":142},"\"MOS score {{ $value }} below 3.5 on {{ $labels.instance }}\"\n",[125,1202,1204],{"class":127,"line":1203},42,[125,1205,302],{"emptyLinePlaceholder":301},[125,1207,1209,1211,1213,1215],{"class":127,"line":1208},43,[125,1210,278],{"class":251},[125,1212,901],{"class":247},[125,1214,263],{"class":251},[125,1216,1217],{"class":142},"MediaPacketLossHigh\n",[125,1219,1221,1223,1225],{"class":127,"line":1220},44,[125,1222,291],{"class":247},[125,1224,263],{"class":251},[125,1226,1227],{"class":142},"rtpengine_packet_loss_ratio > 0.03\n",[125,1229,1231,1233,1235],{"class":127,"line":1230},45,[125,1232,931],{"class":247},[125,1234,263],{"class":251},[125,1236,936],{"class":142},[125,1238,1240,1242],{"class":127,"line":1239},46,[125,1241,941],{"class":247},[125,1243,252],{"class":251},[125,1245,1247,1249,1251],{"class":127,"line":1246},47,[125,1248,948],{"class":247},[125,1250,263],{"class":251},[125,1252,953],{"class":142},[125,1254,1256,1258],{"class":127,"line":1255},48,[125,1257,968],{"class":247},[125,1259,252],{"class":251},[125,1261,1263,1265,1267],{"class":127,"line":1262},49,[125,1264,975],{"class":247},[125,1266,263],{"class":251},[125,1268,1269],{"class":142},"\"RTP packet loss {{ $value | humanizePercentage }} — calls impacted\"\n",[125,1271,1273],{"class":127,"line":1272},50,[125,1274,302],{"emptyLinePlaceholder":301},[125,1276,1278,1280,1282,1284],{"class":127,"line":1277},51,[125,1279,278],{"class":251},[125,1281,901],{"class":247},[125,1283,263],{"class":251},[125,1285,1286],{"class":142},"rtpengineCapacityHigh\n",[125,1288,1290,1292,1294],{"class":127,"line":1289},52,[125,1291,291],{"class":247},[125,1293,263],{"class":251},[125,1295,1296],{"class":142},"rtpengine_active_sessions \u002F rtpengine_max_sessions > 0.85\n",[125,1298,1300,1302,1304],{"class":127,"line":1299},53,[125,1301,931],{"class":247},[125,1303,263],{"class":251},[125,1305,1023],{"class":142},[125,1307,1309,1311],{"class":127,"line":1308},54,[125,1310,941],{"class":247},[125,1312,252],{"class":251},[125,1314,1316,1318,1320],{"class":127,"line":1315},55,[125,1317,948],{"class":247},[125,1319,263],{"class":251},[125,1321,1038],{"class":142},[125,1323,1325,1327],{"class":127,"line":1324},56,[125,1326,968],{"class":247},[125,1328,252],{"class":251},[125,1330,1332,1334,1336],{"class":127,"line":1331},57,[125,1333,975],{"class":247},[125,1335,263],{"class":251},[125,1337,1338],{"class":142},"\"rtpengine at {{ $value | humanizePercentage }} capacity\"\n",[19,1340,1342],{"id":1341},"grafana-dashboard-layout","Grafana Dashboard Layout",[15,1344,1345],{},"Structure your Grafana dashboard in four rows:",[15,1347,1348],{},[1349,1350,1351],"strong",{},"Row 1: SIP Signaling Health",[1353,1354,1355,1359,1362,1365],"ul",{},[1356,1357,1358],"li",{},"INVITE rate (calls\u002Fsec) — line graph, 1h window",[1356,1360,1361],{},"SIP 4xx\u002F5xx rate — stat panel with threshold coloring",[1356,1363,1364],{},"Active dialogs — gauge panel",[1356,1366,1367],{},"Registration success rate — stat panel",[15,1369,1370],{},[1349,1371,1372],{},"Row 2: Media Quality",[1353,1374,1375,1378,1381,1384],{},[1356,1376,1377],{},"MOS score distribution by trunk — heatmap",[1356,1379,1380],{},"Packet loss % by carrier — time series",[1356,1382,1383],{},"Jitter ms — time series with threshold line at 50ms",[1356,1385,1386],{},"Active RTP sessions — gauge",[15,1388,1389],{},[1349,1390,1391],{},"Row 3: Infrastructure",[1353,1393,1394,1397,1400],{},[1356,1395,1396],{},"CPU per VoIP node — multi-series line",[1356,1398,1399],{},"Network I\u002FO (bytes\u002Fsec) — time series",[1356,1401,1402],{},"Memory usage — time series",[15,1404,1405],{},[1349,1406,1407],{},"Row 4: Business Metrics",[1353,1409,1410,1413,1416,1419],{},[1356,1411,1412],{},"ASR by trunk — bar gauge",[1356,1414,1415],{},"ACD (average call duration) — stat panel",[1356,1417,1418],{},"Total calls in last 24h — stat panel",[1356,1420,1421],{},"Calls by outcome (Answered\u002FNo Answer\u002FBusy) — pie chart",[19,1423,1425],{"id":1424},"prometheus-scrape-configuration","Prometheus Scrape Configuration",[116,1427,1429],{"className":233,"code":1428,"language":235,"meta":121,"style":121},"# prometheus.yml\nscrape_configs:\n  - job_name: 'kamailio'\n    static_configs:\n      - targets: ['kamailio-1:9494', 'kamailio-2:9494']\n    scrape_interval: 10s\n\n  - job_name: 'asterisk'\n    static_configs:\n      - targets: ['asterisk-1:9200', 'asterisk-2:9200']\n    scrape_interval: 30s\n\n  - job_name: 'rtpengine'\n    static_configs:\n      - targets: ['rtpengine-1:9900', 'rtpengine-2:9900']\n    scrape_interval: 10s\n\n  - job_name: 'coturn'\n    static_configs:\n      - targets: ['turn-1:9641']\n    scrape_interval: 30s\n\n  - job_name: 'node'\n    static_configs:\n      - targets: ['kamailio-1:9100', 'asterisk-1:9100', 'rtpengine-1:9100']\n    scrape_interval: 15s\n",[104,1430,1431,1436,1443,1455,1462,1484,1494,1498,1509,1515,1533,1542,1546,1557,1563,1581,1589,1593,1604,1610,1623,1631,1635,1646,1652,1675],{"__ignoreMap":121},[125,1432,1433],{"class":127,"line":128},[125,1434,1435],{"class":131},"# prometheus.yml\n",[125,1437,1438,1441],{"class":127,"line":135},[125,1439,1440],{"class":247},"scrape_configs",[125,1442,252],{"class":251},[125,1444,1445,1447,1450,1452],{"class":127,"line":153},[125,1446,257],{"class":251},[125,1448,1449],{"class":247},"job_name",[125,1451,263],{"class":251},[125,1453,1454],{"class":142},"'kamailio'\n",[125,1456,1457,1460],{"class":127,"line":164},[125,1458,1459],{"class":247},"    static_configs",[125,1461,252],{"class":251},[125,1463,1464,1466,1469,1472,1475,1478,1481],{"class":127,"line":175},[125,1465,278],{"class":251},[125,1467,1468],{"class":247},"targets",[125,1470,1471],{"class":251},": [",[125,1473,1474],{"class":142},"'kamailio-1:9494'",[125,1476,1477],{"class":251},", ",[125,1479,1480],{"class":142},"'kamailio-2:9494'",[125,1482,1483],{"class":251},"]\n",[125,1485,1486,1489,1491],{"class":127,"line":189},[125,1487,1488],{"class":247},"    scrape_interval",[125,1490,263],{"class":251},[125,1492,1493],{"class":142},"10s\n",[125,1495,1496],{"class":127,"line":202},[125,1497,302],{"emptyLinePlaceholder":301},[125,1499,1500,1502,1504,1506],{"class":127,"line":213},[125,1501,257],{"class":251},[125,1503,1449],{"class":247},[125,1505,263],{"class":251},[125,1507,1508],{"class":142},"'asterisk'\n",[125,1510,1511,1513],{"class":127,"line":316},[125,1512,1459],{"class":247},[125,1514,252],{"class":251},[125,1516,1517,1519,1521,1523,1526,1528,1531],{"class":127,"line":326},[125,1518,278],{"class":251},[125,1520,1468],{"class":247},[125,1522,1471],{"class":251},[125,1524,1525],{"class":142},"'asterisk-1:9200'",[125,1527,1477],{"class":251},[125,1529,1530],{"class":142},"'asterisk-2:9200'",[125,1532,1483],{"class":251},[125,1534,1535,1537,1539],{"class":127,"line":332},[125,1536,1488],{"class":247},[125,1538,263],{"class":251},[125,1540,1541],{"class":142},"30s\n",[125,1543,1544],{"class":127,"line":337},[125,1545,302],{"emptyLinePlaceholder":301},[125,1547,1548,1550,1552,1554],{"class":127,"line":349},[125,1549,257],{"class":251},[125,1551,1449],{"class":247},[125,1553,263],{"class":251},[125,1555,1556],{"class":142},"'rtpengine'\n",[125,1558,1559,1561],{"class":127,"line":359},[125,1560,1459],{"class":247},[125,1562,252],{"class":251},[125,1564,1565,1567,1569,1571,1574,1576,1579],{"class":127,"line":364},[125,1566,278],{"class":251},[125,1568,1468],{"class":247},[125,1570,1471],{"class":251},[125,1572,1573],{"class":142},"'rtpengine-1:9900'",[125,1575,1477],{"class":251},[125,1577,1578],{"class":142},"'rtpengine-2:9900'",[125,1580,1483],{"class":251},[125,1582,1583,1585,1587],{"class":127,"line":376},[125,1584,1488],{"class":247},[125,1586,263],{"class":251},[125,1588,1493],{"class":142},[125,1590,1591],{"class":127,"line":630},[125,1592,302],{"emptyLinePlaceholder":301},[125,1594,1595,1597,1599,1601],{"class":127,"line":636},[125,1596,257],{"class":251},[125,1598,1449],{"class":247},[125,1600,263],{"class":251},[125,1602,1603],{"class":142},"'coturn'\n",[125,1605,1606,1608],{"class":127,"line":642},[125,1607,1459],{"class":247},[125,1609,252],{"class":251},[125,1611,1612,1614,1616,1618,1621],{"class":127,"line":648},[125,1613,278],{"class":251},[125,1615,1468],{"class":247},[125,1617,1471],{"class":251},[125,1619,1620],{"class":142},"'turn-1:9641'",[125,1622,1483],{"class":251},[125,1624,1625,1627,1629],{"class":127,"line":654},[125,1626,1488],{"class":247},[125,1628,263],{"class":251},[125,1630,1541],{"class":142},[125,1632,1633],{"class":127,"line":660},[125,1634,302],{"emptyLinePlaceholder":301},[125,1636,1637,1639,1641,1643],{"class":127,"line":666},[125,1638,257],{"class":251},[125,1640,1449],{"class":247},[125,1642,263],{"class":251},[125,1644,1645],{"class":142},"'node'\n",[125,1647,1648,1650],{"class":127,"line":672},[125,1649,1459],{"class":247},[125,1651,252],{"class":251},[125,1653,1654,1656,1658,1660,1663,1665,1668,1670,1673],{"class":127,"line":678},[125,1655,278],{"class":251},[125,1657,1468],{"class":247},[125,1659,1471],{"class":251},[125,1661,1662],{"class":142},"'kamailio-1:9100'",[125,1664,1477],{"class":251},[125,1666,1667],{"class":142},"'asterisk-1:9100'",[125,1669,1477],{"class":251},[125,1671,1672],{"class":142},"'rtpengine-1:9100'",[125,1674,1483],{"class":251},[125,1676,1677,1679,1681],{"class":127,"line":683},[125,1678,1488],{"class":247},[125,1680,263],{"class":251},[125,1682,1683],{"class":142},"15s\n",[19,1685,1687],{"id":1686},"storage-sizing-for-voip-metrics","Storage Sizing for VoIP Metrics",[15,1689,1690],{},"VoIP monitoring generates high-cardinality metrics — per-call, per-trunk, per-carrier labels multiply metric series. Calculate your Prometheus storage requirements:",[1353,1692,1693,1696,1699,1702,1705,1708,1711],{},[1356,1694,1695],{},"Samples per scrape: ~500 (typical VoIP stack)",[1356,1697,1698],{},"Scrape interval: 10s → 6 scrapes\u002Fminute",[1356,1700,1701],{},"Samples\u002Fminute: 3,000",[1356,1703,1704],{},"Samples\u002Fday: 4,320,000",[1356,1706,1707],{},"Prometheus bytes per sample: ~1.5 bytes (compressed)",[1356,1709,1710],{},"Storage\u002Fday: ~6 MB",[1356,1712,1713],{},"90-day retention: ~540 MB",[15,1715,1716],{},"This fits comfortably on any VPS. For longer retention or higher cardinality (1,000+ trunks), use Thanos or Mimir to offload to object storage and query across retention windows.",[1718,1719,1720],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":121,"searchDepth":135,"depth":135,"links":1722},[1723,1724,1725,1729,1730,1731,1732,1733],{"id":21,"depth":135,"text":22},{"id":98,"depth":135,"text":99},{"id":386,"depth":135,"text":387,"children":1726},[1727,1728],{"id":394,"depth":153,"text":395},{"id":538,"depth":153,"text":539},{"id":736,"depth":135,"text":737},{"id":860,"depth":135,"text":861},{"id":1341,"depth":135,"text":1342},{"id":1424,"depth":135,"text":1425},{"id":1686,"depth":135,"text":1687},"Architecture","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1504868584819-f8e8b4b6d7e3?w=1200&q=80","2026-02-01","Build a production VoIP observability stack with Prometheus and Grafana: Kamailio stats, Asterisk metrics via snmp-exporter, RTP quality dashboards, and SLA alerting rules.","md",{},"\u002Fblog\u002Fvoip-monitoring-prometheus-grafana",{"title":5,"description":1737},"blog\u002Fvoip-monitoring-prometheus-grafana",[1744,1745,1746,1747,1748,1749,1750],"prometheus","grafana","voip-monitoring","observability","kamailio","asterisk","rtp","IEEtiIKoFkLLUEfqYfeAeFjDBfS_VO2b8QZL6Zu7Jso",1776974166863]