towerops/lib/towerops_native.ex
Graham McIntire 2653e2516d dialyzer: expand PLT, drop blanket codebase suppression
Ignore file was silencing every warning under lib/towerops/**. Root
cause was plt_add_deps: :apps_direct missing Plug/Phoenix/Oban/Decimal
/Redix/ssl/public_key — hundreds of false `unknown_function` warnings
were being papered over.

Expand plt_add_apps to cover the common deps, narrow the ignore file
to real dep-PLT gaps only, and fix two concrete bugs uncovered by the
change:

- ScopedResource.fetch_preload/4: spec was atom() | [atom()] but
  callers pass keyword lists (e.g. [rules: :targets]).
- ToweropsNative: tag NIF stubs with @dialyzer :nowarn_function so
  dialyzer trusts the @spec instead of the fallback body.

Remaining 328 real warnings surface for follow-up.
2026-04-21 09:33:22 -05:00

105 lines
2.9 KiB
Elixir

defmodule ToweropsNative do
@moduledoc """
NIF wrapper for SNMP MIB resolution using pure C + libnetsnmp.
This module provides fast MIB name resolution by directly calling
the industry-standard net-snmp library through a pure C NIF (no Rust layer).
## Examples
iex> ToweropsNative.resolve_oid("sysDescr")
"1.3.6.1.2.1.1.1"
iex> ToweropsNative.resolve_oid("IF-MIB::ifDescr")
"1.3.6.1.2.1.2.2.1.2"
## Performance
- MIB loading: ~1-2 seconds at startup (one-time cost)
- Resolution: ~1-20µs per OID (in-memory lookups)
- Throughput: ~50,000-100,000 resolutions/second per core
"""
@on_load :load_nif
# NIF stubs return fallback values when the NIF fails to load (defensive).
# Dialyzer only sees the stub bodies, so tell it to trust the @spec instead.
@dialyzer {:nowarn_function, [resolve_oid: 1, init_mib_library: 0, load_mib_directory: 1]}
def load_nif do
nif_path = :filename.join(:code.priv_dir(:towerops), ~c"towerops_nif")
case :erlang.load_nif(nif_path, 0) do
:ok ->
:ok
{:error, _reason} ->
require Logger
Logger.warning("ToweropsNative NIF not available, using stubs")
:ok
end
end
@doc """
Resolve MIB name to numeric OID string.
Accepts both simple names ("sysDescr") and module-qualified names
("SNMPv2-MIB::sysDescr").
Returns the numeric OID string on success or `{:error, reason}` if the
name cannot be resolved.
## Examples
iex> ToweropsNative.resolve_oid("sysDescr")
"1.3.6.1.2.1.1.1"
iex> ToweropsNative.resolve_oid("SNMPv2-MIB::sysDescr")
"1.3.6.1.2.1.1.1"
iex> ToweropsNative.resolve_oid("invalidMibName")
{:error, "Failed to resolve MIB name"}
"""
@spec resolve_oid(String.t()) :: String.t() | {:error, String.t()}
def resolve_oid(_mib_name), do: {:error, "NIF not loaded"}
@doc """
Add a MIB directory to the search path.
This function should be called once at application startup to configure
where net-snmp should look for MIB files.
Returns `:ok` on success.
## Examples
iex> ToweropsNative.load_mib_directory("/path/to/mibs")
:ok
"""
@spec load_mib_directory(Path.t()) :: :ok
def load_mib_directory(_path), do: :ok
@doc """
Initialize the net-snmp library and load all MIBs into memory.
This function is called automatically on the first call to `resolve_oid/1`,
so you typically don't need to call it explicitly. However, you can call it
during application startup to warm up the MIB cache.
The initialization is performed on a dirty CPU scheduler to avoid blocking
the main BEAM schedulers.
Returns `"initialized"` or `"already_initialized"`.
## Examples
iex> ToweropsNative.init_mib_library()
"initialized"
iex> ToweropsNative.init_mib_library()
"already_initialized"
"""
@spec init_mib_library() :: String.t()
def init_mib_library, do: "initialized"
end