How to Monitor eVision with Vigilmon
eVision provides Elixir bindings for OpenCV, the world's most widely used computer vision library. It enables image detection, face recognition, real-time video processing, and a broad range of machine vision capabilities directly from your Elixir or Phoenix application — without leaving the BEAM.
But eVision depends on native OpenCV binaries compiled against your system libraries. A version mismatch, a missing shared library, or a GPU-accelerated operation falling back to CPU can silently degrade inference latency. External monitoring surfaces what internal supervision trees can't: that the full vision pipeline works end-to-end from the outside.
This tutorial covers:
- A health endpoint that validates OpenCV availability via eVision
- HTTP uptime monitoring with Vigilmon
- Heartbeat monitoring to detect when vision processing pipelines stall
- Slack alerts and a status page
Step 1: Add eVision to your project
# mix.exs
defp deps do
[
{:evision, "~> 0.2"},
{:req, "~> 0.4"} # for heartbeat pings
]
end
eVision compiles OpenCV as part of the Hex package. The first mix deps.get && mix deps.compile will take several minutes. Ensure you have the build prerequisites:
# Debian/Ubuntu
apt-get install -y cmake build-essential python3
# After deps.compile, verify eVision loaded
mix run --no-halt -e 'IO.inspect(Evision.getBuildInformation())'
A basic image read and operation:
img = Evision.imread("image.jpg")
gray = Evision.cvtColor(img, Evision.Constant.cv_COLOR_BGR2GRAY())
Evision.imwrite("gray.jpg", gray)
Step 2: Build a health endpoint that validates OpenCV
Add a plug that runs a live eVision probe and reports the result:
# lib/my_app_web/plugs/health_check.ex
defmodule MyAppWeb.Plugs.HealthCheck do
import Plug.Conn
def init(opts), do: opts
def call(%Plug.Conn{request_path: "/health"} = conn, _opts) do
checks = run_checks()
status = if checks.status == "ok", do: 200, else: 503
conn
|> put_resp_content_type("application/json")
|> send_resp(status, Jason.encode!(checks))
|> halt()
end
def call(conn, _opts), do: conn
defp run_checks do
evision = evision_check()
memory = memory_check()
all_ok = evision.status == "ok" and memory.status == "ok"
%{
status: if(all_ok, do: "ok", else: "degraded"),
evision: evision,
memory: memory
}
end
defp evision_check do
start = System.monotonic_time(:millisecond)
result =
try do
# Create a small test mat, apply a color conversion, verify output
mat = Evision.Mat.zeros({8, 8, 3}, :u8)
gray = Evision.cvtColor(mat, Evision.Constant.cv_COLOR_BGR2GRAY())
elapsed = System.monotonic_time(:millisecond) - start
if is_struct(gray, Evision.Mat) do
%{status: "ok", latency_ms: elapsed}
else
%{status: "error", reason: "unexpected output type"}
end
rescue
e -> %{status: "error", reason: Exception.message(e)}
end
result
end
defp memory_check do
case :memsup.get_system_memory_data() do
[] ->
%{status: "ok"}
data ->
total = Keyword.get(data, :total_memory, 1)
free = Keyword.get(data, :free_memory, total)
used_pct = (total - free) / total * 100
# OpenCV operations are memory-intensive; warn above 85%
status = if used_pct < 85, do: "ok", else: "warning"
%{status: status, used_pct: Float.round(used_pct, 1)}
end
end
end
Register the plug before your router:
# lib/my_app_web/endpoint.ex
plug MyAppWeb.Plugs.HealthCheck
plug MyAppWeb.Router
Test it:
mix phx.server
curl http://localhost:4000/health | jq .
# {
# "status": "ok",
# "evision": {"status": "ok", "latency_ms": 18},
# "memory": {"status": "ok", "used_pct": 42.3}
# }
Step 3: Track inference metrics with telemetry
Wrap eVision inference calls to emit telemetry on each operation:
# lib/my_app/vision_pipeline.ex
defmodule MyApp.VisionPipeline do
require Logger
def detect_faces(image_path) do
start = System.monotonic_time(:millisecond)
result =
try do
img = Evision.imread(image_path)
gray = Evision.cvtColor(img, Evision.Constant.cv_COLOR_BGR2GRAY())
# Load cascade classifier for face detection
cascade =
Evision.CascadeClassifier.cascadeClassifier(
Application.app_dir(:my_app, "priv/haarcascade_frontalface_default.xml")
)
faces =
Evision.CascadeClassifier.detectMultiScale(
cascade,
gray,
scaleFactor: 1.1,
minNeighbors: 5
)
{:ok, faces}
rescue
e -> {:error, Exception.message(e)}
end
elapsed = System.monotonic_time(:millisecond) - start
status = if match?({:ok, _}, result), do: "ok", else: "error"
:telemetry.execute(
[:my_app, :evision, :detect_faces],
%{duration_ms: elapsed},
%{status: status}
)
if status == "error" do
Logger.error("[eVision] face detection failed in #{elapsed}ms: #{inspect(result)}")
end
result
end
end
Attach a telemetry handler:
# lib/my_app/telemetry.ex
defmodule MyApp.Telemetry do
require Logger
@slow_threshold_ms 5_000
def setup do
:telemetry.attach(
"evision-detect-logger",
[:my_app, :evision, :detect_faces],
&handle_event/4,
nil
)
end
def handle_event(_event, %{duration_ms: ms}, %{status: status}, _cfg) do
if ms >= @slow_threshold_ms do
Logger.warning("[eVision slow inference] #{ms}ms — status=#{status}")
end
end
end
Step 4: Monitor the health endpoint with Vigilmon
- Sign up at vigilmon.online
- Click New Monitor → HTTP
- Enter
https://yourdomain.com/health - Set check interval: 1 minute
- Enable keyword check: body must contain
"status":"ok" - Save
The keyword check is essential: OpenCV native library loading can fail silently after system updates. Without the check, Vigilmon would report HTTP 200 while every computer vision operation raises a NIF load error.
Step 5: Heartbeat monitoring for vision processing pipelines
If you process images or video frames asynchronously — background jobs running face recognition, object detection, or video analysis — a heartbeat confirms the pipeline is alive:
# lib/my_app/workers/vision_pipeline_heartbeat.ex
defmodule MyApp.Workers.VisionPipelineHeartbeat do
use GenServer
require Logger
@interval :timer.minutes(5)
def start_link(_), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
def init(state) do
schedule()
{:ok, state}
end
def handle_info(:run, state) do
run_probe()
schedule()
{:noreply, state}
end
defp run_probe do
try do
# Run a minimal eVision operation to confirm the NIF is functional
mat = Evision.Mat.zeros({4, 4, 3}, :u8)
gray = Evision.cvtColor(mat, Evision.Constant.cv_COLOR_BGR2GRAY())
if is_struct(gray, Evision.Mat) do
ping_vigilmon()
else
Logger.error("[VisionHeartbeat] probe returned unexpected type")
end
rescue
e -> Logger.error("[VisionHeartbeat] probe failed: #{Exception.message(e)}")
end
end
defp ping_vigilmon do
url = Application.get_env(:my_app, :vigilmon)[:vision_heartbeat_url]
if url do
case Req.get(url, receive_timeout: 5_000) do
{:ok, %{status: 200}} -> :ok
error -> Logger.warning("Vigilmon heartbeat failed: #{inspect(error)}")
end
end
end
defp schedule, do: Process.send_after(self(), :run, @interval)
end
Add to your supervision tree:
children = [
MyApp.Repo,
MyAppWeb.Endpoint,
MyApp.Workers.VisionPipelineHeartbeat
]
In Vigilmon, create a Heartbeat Monitor:
- Click New Monitor → Heartbeat
- Set expected interval: 10 minutes
- Copy the ping URL
- Set it as
VIGILMON_VISION_HEARTBEAT_URLin your environment
Step 6: Monitor memory pressure from OpenCV operations
Computer vision operations — especially on high-resolution images or video frames — allocate large native memory buffers outside the BEAM heap. Add memory monitoring to catch pressure before it causes OOM kills:
The health endpoint already includes memory.used_pct. Create a Vigilmon keyword alert on the memory warning state:
- In Vigilmon, click New Monitor → HTTP
- URL:
https://yourdomain.com/health - Enable keyword check: body must NOT contain
"memory":{"status":"warning" - Save
When memory usage exceeds 85%, the health endpoint returns "warning" — Vigilmon alerts you before the OS starts killing processes.
Step 7: Slack alerts and status page
Slack alerts:
- In Vigilmon, go to Notifications → New Channel → Slack
- Paste your Slack incoming webhook URL
- Enable the channel on both your HTTP monitor and heartbeat monitor
When OpenCV fails to initialize:
🔴 DOWN: yourdomain.com/health
Keyword "status":"ok" not found
Detected: EU-West, US-East
2 minutes ago
When the vision pipeline stalls:
🔴 HEARTBEAT MISSED: Vision Processing Pipeline
Expected ping within 10 minutes — none received
Last seen: 19 minutes ago
Status page:
- Go to Status Pages → New Status Page
- Add your health monitor and heartbeat monitor
- Share the URL with your team
What you've built
| What | How |
|------|-----|
| Health endpoint | Plug running a live eVision OpenCV probe |
| Memory check | :memsup-based memory pressure monitoring |
| Keyword check | Vigilmon keyword match on "status":"ok" |
| Uptime monitoring | Vigilmon HTTP monitor → /health |
| Pipeline liveness | Heartbeat GenServer + Vigilmon heartbeat monitor |
| Slow inference detection | Telemetry handler with threshold logging |
| Slack alerts | Vigilmon Slack notification channel |
| Status page | Vigilmon public status page |
eVision gives your Elixir apps computer vision. Vigilmon tells you when the eyes go dark.
Next steps
- Add per-operation heartbeat monitors if you have separate queues for face detection, object detection, and video frame processing
- Track inference latency percentiles over time to detect model performance regressions without code changes
- Use Vigilmon's response time history to correlate eVision latency with upstream image resolution changes
- Add a dedicated memory monitor with a tighter threshold for GPU-accelerated deployments where VRAM exhaustion is a separate failure mode
Get started free at vigilmon.online.