Skip to content

Commit 24b06a3

Browse files
authored
feat(pairing): suport xandra telemery (#1465)
Adds visibility over xandra telemetry trough event bouncing Signed-off-by: Luca Zaninotto <luca.zaninotto@secomind.com>
1 parent 5193e63 commit 24b06a3

4 files changed

Lines changed: 216 additions & 0 deletions

File tree

apps/astarte_pairing/lib/astarte_pairing/config.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ defmodule Astarte.Pairing.Config do
7070
type: :boolean,
7171
default: false
7272

73+
@envdoc """
74+
"The handling method for database events. The default is `expose`, which means that the events are exposed trough telemetry. The other possible value, `log`, means that the events are logged instead."
75+
"""
76+
app_env :database_events_handling_method,
77+
:astarte_realm_management,
78+
:database_events_handling_method,
79+
os_env: "DATABASE_EVENTS_HANDLING_METHOD",
80+
type: Astarte.Pairing.Config.TelemetryType,
81+
default: :expose
82+
7383
@doc """
7484
Returns the cassandra node configuration
7585
"""
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#
2+
# This file is part of Astarte.
3+
#
4+
# Copyright 2025 SECO Mind Srl
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
# SPDX-License-Identifier: Apache-2.0
19+
#
20+
21+
defmodule Astarte.Pairing.Config.TelemetryType do
22+
@moduledoc """
23+
The telemetry type that the node should use to report metrics.
24+
"""
25+
26+
use Skogsra.Type
27+
28+
@allowed_strategies ~w(expose log)
29+
30+
@impl Skogsra.Type
31+
def cast(value) when value in @allowed_strategies do
32+
case value do
33+
"expose" -> {:ok, :expose}
34+
"log" -> {:ok, :log}
35+
end
36+
end
37+
38+
@impl Skogsra.Type
39+
def cast(_) do
40+
:error
41+
end
42+
end

apps/astarte_pairing/lib/astarte_pairing_web/telemetry.ex

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ defmodule Astarte.PairingWeb.Telemetry do
2323
import Telemetry.Metrics
2424

2525
alias Astarte.PairingWeb.Telemetry.APIUsage
26+
alias Astarte.PairingWeb.Telemetry.DatabaseEvents
27+
alias Astarte.Pairing.Config
2628

2729
def start_link(arg) do
2830
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
@@ -129,10 +131,57 @@ defmodule Astarte.PairingWeb.Telemetry do
129131
counter("astarte.pairing.unregister_device.count",
130132
tags: [:realm],
131133
description: "Get unregister device requests total count"
134+
),
135+
136+
# Database exception metrics
137+
counter("astarte.pairing.database.execute_query.exception.count",
138+
tags: [:query, :reason, :kind, :stacktrace],
139+
tag_values: &to_valid_values/1,
140+
unit: {:native, :second}
141+
),
142+
counter("astarte.pairing.database.execute_query.stop.count",
143+
tags: [:query, :reason],
144+
tag_values: &to_valid_values/1,
145+
unit: {:native, :second}
146+
),
147+
148+
# Database preparation metrics
149+
counter("astarte.pairing.database.prepare_query.exception.count",
150+
tags: [:query, :reason, :kind, :stacktrace],
151+
tag_values: &to_valid_values/1,
152+
unit: {:native, :second}
153+
),
154+
counter("astarte.pairing.database.prepare_query.stop.count",
155+
tags: [:query, :reason],
156+
tag_values: &to_valid_values/1,
157+
unit: {:native, :second}
158+
),
159+
160+
# Database connection metrics
161+
counter(
162+
"astarte.pairing.database.cluster.control_connection.failed_to_connect.count",
163+
tag_values: &to_valid_values/1,
164+
tags: [:cluster_name, :host, :reason]
165+
),
166+
counter("astarte.pairing.database.failed_to_connect.conut",
167+
tag_values: &to_valid_values/1,
168+
tags: [:connection_name, :address, :port]
132169
)
133170
]
134171
end
135172

173+
defp to_valid_values(%{query: query, reason: reason}) do
174+
%{query: query.statement, reason: Xandra.Error.message(reason)}
175+
end
176+
177+
defp to_valid_values(%{cluster_name: cluster_name, host: host, reason: reason}) do
178+
%{cluster_name: cluster_name, host: inspect(host), reason: to_string(reason)}
179+
end
180+
181+
defp to_valid_values(%{connection_name: connection_name, address: address, port: port}) do
182+
%{connection_name: connection_name, address: inspect(address), port: inspect(port)}
183+
end
184+
136185
defp periodic_measurements do
137186
[
138187
# A module, function and arguments to be invoked periodically.
@@ -143,6 +192,36 @@ defmodule Astarte.PairingWeb.Telemetry do
143192

144193
defp attach_handlers do
145194
:telemetry.attach(APIUsage, [:cowboy, :request, :stop], &APIUsage.handle_event/4, nil)
195+
196+
:telemetry.attach_many(
197+
DatabaseEvents,
198+
xandra_events(),
199+
&DatabaseEvents.handle_event/4,
200+
Config.database_events_handling_method!()
201+
)
202+
end
203+
204+
defp xandra_events do
205+
[
206+
[:xandra, :connected],
207+
[:xandra, :disconnected],
208+
[:xandra, :failed_to_connect],
209+
[:xandra, :prepared_cache, :hit],
210+
[:xandra, :prepared_cache, :miss],
211+
[:xandra, :prepare_query, :stop],
212+
[:xandra, :execute_query, :stop],
213+
[:xandra, :client_timeout],
214+
[:xandra, :timed_out_response],
215+
[:xandra, :server_warnings],
216+
[:xandra, :cluster, :change_event],
217+
[:xandra, :cluster, :control_connection, :connected],
218+
[:xandra, :cluster, :control_connection, :disconnected],
219+
[:xandra, :cluster, :control_connection, :failed_to_connect],
220+
[:xandra, :cluster, :pool, :started],
221+
[:xandra, :cluster, :pool, :restarted],
222+
[:xandra, :cluster, :pool, :stopped],
223+
[:xandra, :cluster, :discovered_peers]
224+
]
146225
end
147226

148227
defp extract_phoenix_buckets_metadata(%{
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#
2+
# This file is part of Astarte.
3+
#
4+
# Copyright 2025 SECO Mind Srl
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
# SPDX-License-Identifier: Apache-2.0
19+
#
20+
21+
defmodule Astarte.PairingWeb.Telemetry.DatabaseEvents do
22+
@moduledoc """
23+
Telemetry handler for database events.
24+
25+
This module listens to database events and emits telemetry events
26+
with the relevant measurements and metadata.
27+
"""
28+
29+
@bounce_events [
30+
[:prepare_query, :stop],
31+
[:prepare_query, :exception],
32+
[:execute, :stop],
33+
[:execute, :exception],
34+
[:cluster, :control_connection, :failed_to_connect],
35+
[:failed_to_connect]
36+
]
37+
38+
require Logger
39+
alias Astarte.PairingWeb.TelemetryTaskSupervisor
40+
41+
@doc """
42+
Handles telemetry events related to database operations.
43+
44+
See the documentation of Xandra for more details on the events:
45+
https://hexdocs.pm/xandra/telemetry-events.html
46+
47+
This handler drops the `:xandra` prefix from the event name and
48+
executes the telemetry event with the provided measurements and metadata.
49+
"""
50+
def handle_event([:xandra | event], measurements, metadata, :expose) do
51+
with :bounce <- validate_event(event) do
52+
Task.Supervisor.start_child(TelemetryTaskSupervisor, fn ->
53+
with :ok <- filter_event(event, metadata) do
54+
:telemetry.execute(
55+
[:astarte, :pairing, :database] ++ event,
56+
measurements,
57+
metadata
58+
)
59+
end
60+
end)
61+
end
62+
end
63+
64+
def handle_event(event, measurements, metadata, :log) do
65+
Xandra.Telemetry.handle_event(event, measurements, metadata, :no_config)
66+
end
67+
68+
defp validate_event(event) do
69+
case event in @bounce_events do
70+
true -> :bounce
71+
false -> :ok
72+
end
73+
end
74+
75+
defp filter_event([:execute_query, _], metadata), do: has_reason(metadata)
76+
defp filter_event([:prepare_query, _], metadata), do: has_reason(metadata)
77+
defp filter_event(_event, _metadata), do: :ok
78+
79+
defp has_reason(metadata) do
80+
case Map.has_key?(metadata, :reason) do
81+
true -> :ok
82+
false -> :do_not_bounce
83+
end
84+
end
85+
end

0 commit comments

Comments
 (0)