Skip to main content

OmniCHF Operations Guide

Table of Contents

  1. Component Overview
  2. 3GPP Role and Spec References
  3. SBI Endpoints
  4. Configuration Reference
  5. Key Procedures
  6. Observability
  7. Known Limitations
  8. Troubleshooting

Component Overview

OmniCHF implements the Charging Function (CHF) network function defined in 3GPP TS 32.291. The CHF provides converged online and offline charging for 5G PDU sessions via the Nchf_ConvergedCharging service. It translates 5G charging requests into CGRateS SessionS JSON-RPC calls for credit authorization and session management, and generates Call Detail Records (CDRs) on session release.

Charging Session Lifecycle

Each PDU session maps to one charging session, tracked by a chargingDataRef (UUID). Session state is held in an in-memory Agent and is not persisted. A restart loses all active session state.

StateTriggerStorage action
CreatedPOST /chargingdataContext created, CGRateS InitiateSession called
UpdatedPOST /chargingdata/:ref/updateContext updated (usage accumulated, sequence incremented)
ReleasedPOST /chargingdata/:ref/releaseCDR built and logged, CGRateS TerminateSession called, context deleted

3GPP Role and Spec References

ItemReference
CHF NF definition3GPP TS 23.501 Section 6.2.16
Nchf_ConvergedCharging service3GPP TS 32.291
Charging Data Create procedure3GPP TS 32.291 Section 6.1.3.2.1
Charging Data Update procedure3GPP TS 32.291 Section 6.1.3.2.2
Charging Data Release procedure3GPP TS 32.291 Section 6.1.3.2.3
ChargingDataRequest / Response data model3GPP TS 32.291 Section 6.1.6
CDR format for 5G PDU sessions3GPP TS 32.290
SBI common framework3GPP TS 29.500
NF registration with NRF3GPP TS 29.510

SBI Endpoints

Base path: /nchf-convergedcharging/v3

MethodPathDescriptionSuccessError
POST/chargingdataCreate a charging session (Initial request). Allocates a chargingDataRef, starts a CGRateS session, and returns granted units.201 Created with Location header500 Internal Server Error
POST/chargingdata/{chargingDataRef}/updateUpdate a charging session (Interim request). Reports current usage and requests additional credit.200 OK404 Not Found, 500 Internal Server Error
POST/chargingdata/{chargingDataRef}/releaseRelease a charging session (Final request). Reports final usage, generates CDR, terminates CGRateS session.204 No Content404 Not Found, 500 Internal Server Error

ChargingDataRequest — Key Fields

FieldTypeUsed inDescription
subscriberIdentifierstringCreate, Update, ReleaseSUPI (e.g., imsi-999700000000001). Used as CGRateS account identifier.
nfConsumerIdentificationobjectCreateNF consumer info. Fallback source for SUPI if subscriberIdentifier is absent.
pDUSessionChargingInformationobjectCreate, Update, ReleasePDU session details: DNN, S-NSSAI, RAT type, QoS, PDU session ID and type.
multipleUnitUsagearrayUpdate, ReleaseReported usage containers. First element's usedUnitContainer is used for volume and duration extraction.
requestTypestringAllINITIAL_REQUEST, UPDATE_REQUEST, or TERMINATION_REQUEST.

ChargingDataResponse — Key Fields

FieldTypePresentDescription
invocationSequenceNumberintegerCreate, UpdateSequence number for this response. Hardcoded to 1 on Create (see CHF-M1). Incremented on each Update.
invocationResultobjectCreate, UpdateAlways {"resultCode": "SUCCESS"} on the happy path.
sessionIdstringCreate, UpdateThe chargingDataRef (UUID) allocated for this session.
multipleUnitInformationarrayCreate, UpdateGranted units. Contains one entry with resultCode, grantedUnit (totalVolume, time), and ratingGroup (hardcoded to 1, see CHF-L2).

Configuration Reference

All parameters are set via the application environment (typically config/runtime.exs).

config :omnichf,
sbi_scheme: "http",
sbi_addr: "127.0.0.14",
sbi_port: 7777,
nrf_uri: "http://127.0.0.10:7777",
mcc: "999",
mnc: "70",
heartbeat_interval: 10_000,
cgrates_enabled: false,
cgrates_url: "http://localhost:2080/jsonrpc",
cgrates_tenant: "cgrates.org",
cgrates_timeout: 5000

Parameter Table

ParameterDefaultTypeDescription
sbi_scheme"http"stringTransport scheme for the SBI listener.
sbi_addr"127.0.0.14"stringIP address the SBI HTTP server binds to.
sbi_port7777integerTCP port the SBI HTTP server listens on.
nrf_uri"http://127.0.0.10:7777"stringBase URI of the NRF used for NF registration and heartbeat.
mcc"999"stringMobile Country Code. Used in the NF profile submitted to the NRF and in the serving network name.
mnc"70"stringMobile Network Code. Used in the NF profile submitted to the NRF and in the serving network name.
heartbeat_interval10_000integer (ms)Interval between NRF heartbeat requests.
cgrates_enabledfalsebooleanMaster switch for CGRateS integration. When false, all CGRateS calls are skipped and a default grant of 86,400 units is returned. Set to true in production when a CGRateS instance is available.
cgrates_url"http://localhost:2080/jsonrpc"stringJSON-RPC endpoint URL for the CGRateS instance. Only used when cgrates_enabled is true.
cgrates_tenant"cgrates.org"stringCGRateS tenant name. Passed in all SessionS API calls as the Tenant field. Must match the tenant configured in CGRateS.
cgrates_timeout5000integer (ms)HTTP request timeout for CGRateS JSON-RPC calls. Health check calls use min(cgrates_timeout, 3000) to avoid blocking the health endpoint.

CGRateS Integration Notes

When cgrates_enabled is false, OmniCHF operates in bypass mode: all charging requests are accepted and granted 86,400 units (time or volume) without any rating or authorization. This is suitable for lab and integration testing when CGRateS is not available.

CGRateS communication uses Erlang's built-in :httpc HTTP client (see limitation CHF-M5). This client does not support connection pooling. Under high load, each CGRateS request opens and closes a new HTTP connection, which may become a bottleneck.


Key Procedures

Charging Session Create (Initial)

Charging Session Update (Interim)

Charging Session Release (Final)

CGRateS Event Mapping

OmniCHF maps 5G charging fields to CGRateS SessionS event fields as follows:

CGRateS FieldSourceNotes
AccountsubscriberIdentifier (SUPI)Falls back to nfConsumerIdentification.supi
SubjectsubscriberIdentifier (SUPI)Same as Account
DestinationpduSessionInformation.dnnId or .dnnData Network Name
ToRpduSessionInformation.pduTypeAlways *data for PDU sessions
RequestTyperequestTypeAlways mapped to *prepaid
UsageusedUnitContainer.totalVolume or sum of uplink+downlink or timeFirst non-zero value wins
OriginIDchargingDataRefUnique UUID per session
OriginHoststatic "OmniCHF"
SUPIsubscriberIdentifier5G extension field
DNNpduSessionInformation.dnnId5G extension field
S-NSSAI_SSTpduSessionInformation.sNSSAI.sst5G extension field
S-NSSAI_SDpduSessionInformation.sNSSAI.sd5G extension field
5QIpduSessionInformation.qoSInformation.5qI5G extension field
RATTypepduSessionInformation.ratTypeDefault: "NR"
PDUSessionIDpduSessionInformation.pduSessionID5G extension field
PDUSessionTypepduSessionInformation.pduTypeDefault: "IPV4"

CDR Fields

CDRs are built on session release and emitted to the application log at INFO level. The CDR map contains:

FieldSource
record_typeStatic: "5G_PDU_SESSION"
supiCharging context
dnnpduSessionInformation.dnnId or .dnn
snssai{sst, sd} from pduSessionInformation.sNSSAI
qos_5qipduSessionInformation.qoSInformation.5qI
rat_typepduSessionInformation.ratType
pdu_session_idCharging context
pdu_session_typepduSessionInformation.pduType
volume_uplinkusedUnitContainer.uplinkVolume
volume_downlinkusedUnitContainer.downlinkVolume
volume_totalusedUnitContainer.totalVolume (or uplink+downlink)
durationusedUnitContainer.time (or wall clock diff if zero)
start_timeSession created_at timestamp
end_timeWall clock at release time
charging_data_refSession UUID

Observability

Telemetry Events

EventMeasurementsTagsDescription
[:omnichf, :charging, :initial]countsupiFired on every Create request
[:omnichf, :charging, :update]countrefFired on every Update request
[:omnichf, :charging, :release]countrefFired on every Release request
[:omnichf, :charging, :creates]countresult (success/failure)Create operation outcome
[:omnichf, :charging, :updates]countresultUpdate operation outcome
[:omnichf, :charging, :releases]countresultRelease operation outcome
[:omnichf, :cgrates, :request]count, duration_msoperation, resultPer CGRateS JSON-RPC call
[:omnichf, :cgrates, :health]status (1/0)CGRateS connectivity health
[:omnichf, :sessions, :active]countGauge: active charging sessions
[:omni5g, :nrf, :registration]status (1/0)nf_typeNRF registration status

Prometheus Metrics

Charging Metrics

MetricTypeTagsDescription
omni_chf.charging.initial.countcountersupiCharging session creates
omni_chf.charging.update.countcounterrefCharging session updates
omni_chf.charging.release.countcounterrefCharging session releases
omni_chf.charging.creates.totalcounterresultTotal charging session creates
omni_chf.charging.updates.totalcounterresultTotal charging session updates
omni_chf.charging.releases.totalcounterresultTotal charging session releases
omni_chf.sessions.active.countgauge--Number of active charging sessions

CGRateS Metrics

MetricTypeTagsDescription
omni_chf.cgrates.calls.countcountermethod, resultCGRateS JSON-RPC calls
omni_chf.cgrates.latency.millisecondsgauge--CGRateS call latency
omni_chf.cgrates.healthgauge--CGRateS connectivity health (1=up, 0=down)
omni_chf.cgrates.requests.totalcounteroperation, resultTotal CGRateS JSON-RPC requests
omni_chf.cgrates.request.duration_msdistributionoperationCGRateS request duration in ms (buckets: 5, 10, 25, 50, 100, 250, 500, 1000, 2500)

NRF Metrics

MetricTypeTagsDescription
omni_chf.nrf.registration.statusgaugenf_typeNRF registration status (1=registered, 0=not)

BEAM VM Metrics

MetricTypeDescription
beam.memory.totalgaugeTotal BEAM memory in bytes
beam.memory.processesgaugeMemory used by Erlang processes
beam.memory.processes_usedgaugeMemory actually used by processes
beam.memory.systemgaugeSystem memory
beam.memory.atomgaugeTotal atom memory
beam.memory.atom_usedgaugeUsed atom memory
beam.memory.binarygaugeBinary memory
beam.memory.codegaugeCode memory
beam.memory.etsgaugeETS table memory
beam.processes.countgaugeNumber of Erlang processes
beam.ports.countgaugeNumber of Erlang ports
beam.atom.countgaugeNumber of atoms
beam.vm.uptimegaugeVM uptime in seconds

Log Patterns

LevelPatternMeaning
infoCHF Create: ref=<UUID> supi=<SUPI> pdu_session=<N>Successful Create initiated
infoCHF Update: ref=<UUID>Update request received
infoCHF Release: ref=<UUID>Release request received
infoCHF CDR: %{...}CDR emitted on release
infoInitiating CGRateS session for <ref>, account: &lt;SUPI>CGRateS InitiateSession sent
infoCGRateS authorized &lt;N> units for session <ref>Credit granted
infoCGRateS session <ref> terminated successfullyCGRateS TerminateSession OK
warningCGRateS integration disabled, returning default authorizationBypass mode active
warningCHF Update: unknown ref=&lt;UUID>Update for non-existent session
warningCHF Release: unknown ref=&lt;UUID>Release for non-existent session
errorCHF Create failed: <reason>Create operation failed
errorCHF Update failed: <reason>Update operation failed
errorCGRateS InitiateSession failed for <ref>: <reason>CGRateS error on create
errorCGRateS HTTP error <status>: <body>Non-200 from CGRateS
errorCGRateS HTTP request failed: <reason>Network error to CGRateS

Known Limitations

IDSeverityDescription
CHF-M1MediuminvocationSequenceNumber is hardcoded to 1 in the Create (Initial) response. Per TS 32.291 the sequence number should start at 1 and increment on subsequent responses, which it does on Update. The issue is that if a consumer sends a Create with a invocationSequenceNumber in the request, that value is not inspected or validated.
CHF-M3MediuminvocationTimeStamp is absent from ChargingDataResponse. Per TS 32.291 this field is mandatory in the response body. Strict consumers that require this field will receive an incomplete response.
CHF-M5MediumThe CGRateS client uses Erlang's :httpc HTTP client rather than Finch. :httpc does not support connection pooling; each JSON-RPC call opens and closes a TCP connection. Under load (many concurrent charging sessions), CGRateS call latency will increase and the connection setup overhead becomes significant. Monitor omni_chf.cgrates.request.duration_ms.
CHF-L1LowNo triggers field is included in ChargingDataResponse. Per TS 32.291, triggers can instruct the SMF to send an interim update on specific events (volume threshold, time threshold, QoS change). Without triggers, the SMF uses its own local policy to determine when to send updates.
CHF-L2LowratingGroup in multipleUnitInformation is hardcoded to 1. Real deployments typically have multiple rating groups per PDU session (one per service data flow). All usage is attributed to rating group 1 regardless of the ratingGroup values in the request's multipleUnitUsage.
CHF-L3LowNo chargingId in the 3GPP format is generated. Per TS 32.290, charging records should carry a chargingId that correlates with the PDU session ID assigned by the SMF. The charging_data_ref UUID is used instead, which may cause correlation issues in downstream billing systems that expect the 3GPP chargingId format.
CHF-L4LowThe CDR record is missing the chargingID and recordingEntity fields required by TS 32.290. Downstream mediation or billing systems expecting these fields will need to tolerate their absence or be configured to treat them as optional.
CHF-L5LowOffline charging and CDR file output are not implemented. CDRs are emitted only to the application log via Logger.info. There is no file-based CDR output, no ASN.1 encoding, and no transfer to a billing domain gateway. For production offline charging, a log shipper (e.g., Fluentd, Vector) must collect the CHF CDR log lines and transform them for the billing system.

Troubleshooting

404 on Update or Release

The chargingDataRef does not match any active session in memory. Causes:

  1. OmniCHF restarted between Create and Update/Release — all session state is in-memory and is lost on restart.
  2. The SMF sent the wrong chargingDataRef in the path.
  3. A Release was previously sent for this session, which deleted the context.

Check logs for CHF Update: unknown ref= or CHF Release: unknown ref= to confirm.

500 on Create with CGRateS enabled

The CGRateS call failed. Check:

  1. Is cgrates_url pointing to a reachable CGRateS instance?
  2. Is cgrates_tenant correct? Mismatched tenant names cause CGRateS to return an error response.
  3. Check omni_chf.cgrates.health metric (1=up, 0=down).
  4. Review logs for CGRateS InitiateSession failed for <ref>: <reason> — the reason will be one of: {:cgrates_error, message}, {:http_error, status}, or {:http_error, reason}.
  5. Verify the CGRateS SessionSv1 service is enabled in the CGRateS configuration.

CGRateS health check failing but daemon is running

The health check (via SessionSv1.GetActiveSessions) uses a 3-second capped timeout. If CGRateS is slow to respond, the health check may fail while the service is technically available. Check cgrates_timeout — the cap is min(cgrates_timeout, 3000). Also confirm cgrates_url uses HTTP (not HTTPS) unless TLS is configured.

CDRs not appearing or incomplete

CDRs are written to the application log at INFO level (see limitation CHF-L5). To capture CDRs:

  1. Ensure the application log level is set to info or lower.
  2. Filter log lines containing "CHF CDR:" for post-processing.
  3. Note that CDRs are missing chargingID, recordingEntity (CHF-L4), and will have ratingGroup: 1 for all service data flows (CHF-L2).

High CGRateS call latency

Because CGRateS uses :httpc without connection pooling (CHF-M5), latency increases under load. To diagnose:

  1. Check omni_chf.cgrates.request.duration_ms histogram for p99 latency.
  2. If latency is high under concurrent load, reduce the number of concurrent charging sessions or consider deploying OmniCHF behind a load balancer with multiple instances.
  3. As a workaround, set cgrates_timeout to a value lower than the expected worst-case CGRateS response time to prevent slow CGRateS calls from blocking the Elixir process pool.

Active sessions count not decreasing after releases

If omni_chf.sessions.active.count remains elevated after sessions should have been released:

  1. Check for 404 responses on Release calls — if the SMF receives 404, it may not retry and the SMF considers the session released while OmniCHF may still have the context.
  2. In the inverse case, if OmniCHF restarted and lost session state, contexts are gone but the SMF may still send Update/Release requests that result in 404. This is expected behaviour.

NRF registration not maintained

Check omni_chf.nrf.registration.status metric. If it reads 0:

  1. Verify nrf_uri is correct and the NRF is reachable from OmniCHF's sbi_addr.
  2. Check mcc and mnc match the NRF PLMN configuration.
  3. Review application logs at startup for NRF registration errors.