Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dialyzer_ignore.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
{"lib/observer_web/macros.ex", :unknown_function}
]
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### Enhancements
* [[`PR-6`](https://github.com/thiagoesteves/observer_web/pull/6)] Adjusted Observer Web Font
* [[`PR-7`](https://github.com/thiagoesteves/observer_web/pull/7)] Adding Live Metrics in Oberver Web for capturing the VM statistics using OTP distribution

# Previous Releases
* [0.1.3 🚀 (2025-02-08)](https://github.com/thiagoesteves/observer_web/blob/v0.1.3/CHANGELOG.md)
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

# Observer Web

Observer Web is an easy-to-use tool that integrates into your application to provide observability.
It relies on the OTP distribution to offer tracing using the [Erlang debugger][edb], as well as
Process/Port status and details.
Observer Web is an easy-to-use tool that integrates into your application, providing
enhanced observability. Leveraging OTP distribution, it offers tracing through the
[Erlang debugger][edb], along with detailed insights into process/port statuses
and Beam VM statistics.

Powered by [Phoenix LiveView][liv], it is distributed, lightweight, and fully real-time. This
library is part of the [DeployEx][dye] project.
Expand Down
42 changes: 42 additions & 0 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,48 @@ hooks.ObserverEChart = {
}
}

hooks.LiveMetricsEChart = {
mounted() {
selector = "#" + this.el.id

const dataConfig = JSON.parse(this.el.dataset.config)
const columns = JSON.parse(this.el.dataset.columns)

this.chart = echarts.init(this.el.querySelector(selector + "-chart"))
this.chart.setOption(dataConfig)
this.graph_cols = columns
},
updated() {
const dataConfig = JSON.parse(this.el.dataset.config)
const reset = JSON.parse(this.el.dataset.reset)
const columns = JSON.parse(this.el.dataset.columns)

if (reset) {
this.chart.setOption(dataConfig)

} else {
var option = this.chart.getOption();
var updatedXAxis = option.xAxis[0].data.concat(dataConfig.xAxis.data);
var updatedSeries = option.series.map((series, index) => {
// Concatenate the corresponding dataset to each series
return {
data: series.data.concat(dataConfig.series[index] ? dataConfig.series[index].data : [])
};
});

this.chart.setOption(
{
xAxis: { data: updatedXAxis },
series: updatedSeries
})
}
if (columns != this.columns) {
this.chart.resize()
this.columns = columns
}
}
}

const liveSocket = new LiveSocket(livePath, Socket, {
transport: liveTran === "longpoll" ? LongPoll : WebSocket,
params: { _csrf_token: csrfToken },
Expand Down
7 changes: 4 additions & 3 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ if config_env() == :dev do
),
cd: Path.expand("../assets", __DIR__)
]

config :observer_web, ObserverWeb.Telemetry,
adapter: ObserverWeb.Telemetry.Consumer,
data_retention_period: :timer.minutes(15)
end

# Configures Elixir's Logger
config :logger, level: :warning
config :logger, :console, format: "[$level] $message\n"

config :phoenix, stacktrace_depth: 20

# Rpc Adapter
config :observer_web, ObserverWeb.Rpc, adapter: ObserverWeb.Rpc.Local
2 changes: 2 additions & 0 deletions coveralls.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"lib/web/components/core.ex",
"lib/observer_web/rpc/adapter.ex",
"lib/observer_web/rpc/local.ex",
"lib/observer_web/telemetry/adapter.ex",
"lib/observer_web/macros.ex",
"deps/*",
"test/*"
],
Expand Down
2 changes: 0 additions & 2 deletions dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ Application.put_env(:observer_web, WebDev.Endpoint,
debug_errors: true,
http: [port: port],
live_view: [signing_salt: "eX7TFPY6Y/+XQ1o2pOUW3DjgAoXGTAdX"],
pubsub_server: WebDev.PubSub,
render_errors: [formats: [html: WebDev.ErrorHTML], layout: false],
secret_key_base: "jAu3udxm+8tIRDXLLKo+EupAlEvdLsnNG82O8e9nqylpBM9gP8AjUnZ4PWNttztU",
url: [host: "localhost"],
Expand All @@ -76,7 +75,6 @@ Application.put_env(:phoenix, :persistent, true)

Task.async(fn ->
children = [
{Phoenix.PubSub, [name: WebDev.PubSub, adapter: Phoenix.PubSub.PG2]},
{WebDev.Endpoint, []}
]

Expand Down
17 changes: 17 additions & 0 deletions guides/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,23 @@ After you've verified that the dashboard is loading you'll probably want to rest
dashboard via authentication, either with a [custom resolver's][ac] access controls or [Basic
Auth][ba].

### Retention period for metrics

The Observer Web can monitor Beam VM metrics by default, using ETS tables to store the data.
However, this means that the data is not persisted across restarts. The retention period
for this data can be configured.

By default, without a retention time set, the metrics will only show data received during the
current session. If you'd like to persist this data for a longer period, you can configure
a retention time.

To configure the retention period, use the following optional setting:

```elixir
config :observer_web, ObserverWeb.Telemetry,
data_retention_period: :timer.minutes(5)
```

### Usage with Web and Clustering

The Observer Web provides observer ability for the local application as well as any other that is
Expand Down
10 changes: 7 additions & 3 deletions guides/overview.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Overview

Observer Web is an easy-to-use tool that integrates into your application to provide observability.
It relies on the OTP distribution to offer tracing using the [Erlang debugger][edb], as well as
Process/Port status and details.
Observer Web is an easy-to-use tool that integrates into your application, providing
enhanced observability. Leveraging OTP distribution, it offers tracing through the
[Erlang debugger][edb], along with detailed insights into process/port statuses
and Beam VM statistics.

Powered by [Phoenix LiveView][liv], it is distributed, lightweight, and fully real-time. This
library is part of the [DeployEx][dye] project.
Expand All @@ -22,6 +23,9 @@ and also function callers, as many other possibilities.
- **🔬 Process/Port Inspection** - View processes and ports details as well as their status and
connectivity (and much more).

- **📊 Realtime VM Metrics** - - **📊 Realtime VM Metrics** - Powered by ets table and OTP
distribution, vm memory statistics are stored and easily filtered.

## Installation

See the [installation guide](installation.md) for details on installing and configuring Observer Web
Expand Down
Binary file modified guides/static/dashboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 18 additions & 1 deletion lib/observer_web/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,29 @@ defmodule ObserverWeb.Application do

use Application

import ObserverWeb.Macros

@impl true
def start(_type, _args) do
children = [ObserverWeb.Tracer.Server]
children =
[
{Phoenix.PubSub, [name: ObserverWeb.PubSub]},
ObserverWeb.Tracer.Server
] ++ telemetry_servers()

# # See https://hexdocs.pm/elixir/Supervisor.html
# # for other strategies and supported options
Supervisor.start_link(children, strategy: :one_for_one, name: __MODULE__)
end

# NOTE: DO NOT start these servers when running tests.
if_not_test do
defp telemetry_servers,
do: [
ObserverWeb.Telemetry.Consumer,
ObserverWeb.Telemetry.VmData
]
else
defp telemetry_servers, do: []
end
end
49 changes: 49 additions & 0 deletions lib/observer_web/macros.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
defmodule ObserverWeb.Macros do
@moduledoc """
This file contains common macros
"""

@doc """
Compiler macro that will only add the do block code if it is not a test build
(prod or dev) and will run the else block of code if it is a test build

Note: the else block is optional

## Example:
import ObserverWeb.Macros

if_not_test do
@msg "I am NOT a test build"
else
@msg "I am a test build"
end

"""
@spec if_not_test([{:do, any} | {:else, any}, ...]) :: any
defmacro if_not_test(do: tBlock, else: fBlock) do
case Mix.env() do
# If this is a dev block
:test ->
if nil != fBlock do
quote do
unquote(fBlock)
end
end

# otherwise go with the alternative
_ ->
quote do
unquote(tBlock)
end
end
end

defmacro if_not_test(do: tBlock) do
if :test != Mix.env() do
# If this is a dev block
quote do
unquote(tBlock)
end
end
end
end
75 changes: 75 additions & 0 deletions lib/observer_web/telemetry.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
defmodule ObserverWeb.Telemetry do
@moduledoc """
This module will provide telemetry abstraction
"""

@behaviour ObserverWeb.Telemetry.Adapter

defmodule Data do
@moduledoc """
Structure to handle the telemetry event
"""
@type t :: %__MODULE__{
timestamp: non_neg_integer(),
value: integer() | float(),
unit: String.t(),
tags: map(),
measurements: map()
}

defstruct timestamp: nil,
value: "",
unit: "",
tags: %{},
measurements: %{}
end

### ==========================================================================
### Public functions
### ==========================================================================

@doc """
This function pushes events to the Telemetry module
"""
# coveralls-ignore-start
@spec push_data(any()) :: :ok
def push_data(event), do: default().push_data(event)
# coveralls-ignore-stop

@doc """
Subscribe for new keys notifications
"""
@spec subscribe_for_new_keys() :: :ok | {:error, term}
def subscribe_for_new_keys, do: default().subscribe_for_new_keys()

@doc """
Subscribe for new data notifications for the respective node/key
"""
@spec subscribe_for_new_data(String.t(), String.t()) :: :ok | {:error, term}
def subscribe_for_new_data(node, key), do: default().subscribe_for_new_data(node, key)

@doc """
Unsubscribe for new data notifications for the respective node/key
"""
@spec unsubscribe_for_new_data(String.t(), String.t()) :: :ok
def unsubscribe_for_new_data(node, key), do: default().unsubscribe_for_new_data(node, key)

@doc """
Fetch data by node and key
"""
@spec list_data_by_node_key(atom() | String.t(), String.t(), Keyword.t()) :: list()
def list_data_by_node_key(node, key, options),
do: default().list_data_by_node_key(node, key, options)

@doc """
List all keys registered for the respective node
"""
@spec get_keys_by_node(atom()) :: list()
def get_keys_by_node(node), do: default().get_keys_by_node(node)

### ==========================================================================
### Private functions
### ==========================================================================
defp default,
do: Application.get_env(:observer_web, __MODULE__)[:adapter] || ObserverWeb.Telemetry.Consumer
end
12 changes: 12 additions & 0 deletions lib/observer_web/telemetry/adapter.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule ObserverWeb.Telemetry.Adapter do
@moduledoc """
Behaviour that defines the telemetry adapter callback
"""

@callback push_data(any()) :: :ok
@callback subscribe_for_new_keys() :: :ok | {:error, term}
@callback subscribe_for_new_data(String.t(), String.t()) :: :ok | {:error, term}
@callback unsubscribe_for_new_data(String.t(), String.t()) :: :ok
@callback list_data_by_node_key(atom() | String.t(), String.t(), Keyword.t()) :: list()
@callback get_keys_by_node(atom()) :: list()
end
Loading
Loading