Current section

Files

Jump to
oak lib collector runtime.ex
Raw

lib/collector/runtime.ex

# Copyright 2024 Clivern. All rights reserved.
# Use of this source code is governed by the MIT
# license that can be found in the LICENSE file.
defmodule Oak.Collector.Runtime do
@moduledoc """
Collects runtime metrics from the Elixir/BEAM VM.
This collector provides metrics for:
- Process and port counts
- Memory usage (total, allocated, system)
- Garbage collection statistics
- System information
- Load averages
"""
@doc """
Collects all runtime metrics and returns them as a list of metric structs.
"""
def collect do
[
collect_process_metrics(),
collect_memory_metrics(),
collect_gc_metrics(),
collect_system_metrics(),
collect_load_metrics()
]
|> List.flatten()
end
@doc """
Collects process-related metrics.
"""
def collect_process_metrics do
process_count = Process.list() |> length()
port_count = :erlang.ports() |> length()
[
%Oak.Metric.Gauge{
name: "erlang_processes_total",
help: "Total number of processes",
labels: %{},
value: process_count
},
%Oak.Metric.Gauge{
name: "erlang_ports_total",
help: "Total number of ports",
labels: %{},
value: port_count
}
]
end
@doc """
Collects memory usage metrics.
"""
def collect_memory_metrics do
memory = :erlang.memory()
[
%Oak.Metric.Gauge{
name: "erlang_memory_bytes",
help: "Memory usage in bytes",
labels: %{type: "total"},
value: memory[:total]
},
%Oak.Metric.Gauge{
name: "erlang_memory_bytes",
help: "Memory usage in bytes",
labels: %{type: "processes"},
value: memory[:processes]
},
%Oak.Metric.Gauge{
name: "erlang_memory_bytes",
help: "Memory usage in bytes",
labels: %{type: "system"},
value: memory[:system]
},
%Oak.Metric.Gauge{
name: "erlang_memory_bytes",
help: "Memory usage in bytes",
labels: %{type: "atom"},
value: memory[:atom]
},
%Oak.Metric.Gauge{
name: "erlang_memory_bytes",
help: "Memory usage in bytes",
labels: %{type: "binary"},
value: memory[:binary]
},
%Oak.Metric.Gauge{
name: "erlang_memory_bytes",
help: "Memory usage in bytes",
labels: %{type: "ets"},
value: memory[:ets]
}
]
end
@doc """
Collects garbage collection metrics.
"""
def collect_gc_metrics do
# Get GC info for all processes
gc_info =
Process.list()
|> Enum.map(fn pid ->
case Process.info(pid, [:garbage_collection]) do
[{:garbage_collection, gc}] -> gc
_ -> []
end
end)
|> List.flatten()
# Count GC runs
gc_runs =
gc_info
|> Enum.count(fn {key, _} -> key == :number_of_gcs end)
# Sum GC words
gc_words =
gc_info
|> Enum.filter(fn {key, _} -> key == :words_reclaimed end)
|> Enum.map(fn {_, value} -> value end)
|> Enum.sum()
[
%Oak.Metric.Counter{
name: "erlang_gc_runs_total",
help: "Total number of garbage collections",
labels: %{},
value: gc_runs
},
%Oak.Metric.Counter{
name: "erlang_gc_words_reclaimed_total",
help: "Total words reclaimed by garbage collection",
labels: %{},
value: gc_words
}
]
end
@doc """
Collects system information metrics.
"""
def collect_system_metrics do
[
%Oak.Metric.Gauge{
name: "erlang_system_info",
help: "System information value",
labels: %{type: "process_count"},
value: :erlang.system_info(:process_count)
},
%Oak.Metric.Gauge{
name: "erlang_system_info",
help: "System information value",
labels: %{type: "port_count"},
value: :erlang.system_info(:port_count)
},
%Oak.Metric.Gauge{
name: "erlang_system_info",
help: "System information value",
labels: %{type: "schedulers"},
value: :erlang.system_info(:schedulers)
},
%Oak.Metric.Gauge{
name: "erlang_system_info",
help: "System information value",
labels: %{type: "schedulers_online"},
value: :erlang.system_info(:schedulers_online)
}
]
end
@doc """
Collects system load metrics (Unix-specific).
"""
def collect_load_metrics do
case :os.type() do
{:unix, _} ->
try do
# Get load average from /proc/loadavg on Linux
case File.read("/proc/loadavg") do
{:ok, content} ->
[load1, load5, load15 | _] = String.split(content, " ")
[
%Oak.Metric.Gauge{
name: "system_load_average",
help: "System load average",
labels: %{period: "1m"},
value: String.to_float(load1)
},
%Oak.Metric.Gauge{
name: "system_load_average",
help: "System load average",
labels: %{period: "5m"},
value: String.to_float(load5)
},
%Oak.Metric.Gauge{
name: "system_load_average",
help: "System load average",
labels: %{period: "15m"},
value: String.to_float(load15)
}
]
_ ->
[]
end
rescue
_ -> []
end
_ ->
[]
end
end
end