Packages
electric
1.4.15
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.16
1.4.16-beta-1
1.4.15
1.4.14
1.4.13
1.4.12
1.4.11
1.4.10
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.14
1.1.13
1.1.12
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
retired
1.1.5
retired
1.1.4
retired
1.1.3
retired
1.1.2
1.1.1
1.1.0
1.0.24
1.0.23
1.0.22
1.0.21
1.0.20
1.0.19
1.0.18
1.0.17
1.0.15
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-beta.23
1.0.0-beta.22
1.0.0-beta.20
1.0.0-beta.19
1.0.0-beta.18
1.0.0-beta.17
1.0.0-beta.16
1.0.0-beta.15
1.0.0-beta.14
1.0.0-beta.13
1.0.0-beta.12
1.0.0-beta.11
1.0.0-beta.10
1.0.0-beta.9
1.0.0-beta.8
1.0.0-beta.7
1.0.0-beta.6
1.0.0-beta.5
1.0.0-beta.4
1.0.0-beta.3
1.0.0-beta.2
1.0.0-beta.1
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.1
0.8.0
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.5.2
0.4.4
Postgres sync engine. Sync little subsets of your Postgres data into local apps and services.
Current section
Files
Jump to
Current section
Files
lib/electric/stack_supervisor/telemetry.ex
defmodule Electric.StackSupervisor.Telemetry do
require Logger
def configure(config) do
# Set shared OpenTelemetry span attributes for the given stack. They are stored in
# persistent_term so it doesn't matter which process this function is called from.
telemetry_span_attrs = Access.get(config, :telemetry_span_attrs, %{})
if is_map(telemetry_span_attrs) and map_size(telemetry_span_attrs) > 0 do
Electric.Telemetry.OpenTelemetry.set_stack_span_attrs(
config.stack_id,
telemetry_span_attrs
)
end
child_spec(config)
end
@doc false
# used in tests
def default_periodic_measurements(%{stack_id: stack_id} = config) do
[
{__MODULE__, :count_shapes, [stack_id]},
{__MODULE__, :report_write_buffer_size, [stack_id]},
{__MODULE__, :report_retained_wal_size, [stack_id, config.replication_opts[:slot_name]]},
{__MODULE__, :report_disk_usage, [stack_id]},
{__MODULE__, :report_shape_db_stats, [stack_id]}
]
end
def count_shapes(stack_id, _telemetry_opts) do
# Telemetry is started before everything else in the stack, so we need to handle
# the case where the shape cache is not started yet.
with num_shapes when is_integer(num_shapes) <- Electric.ShapeCache.count_shapes(stack_id) do
Electric.Telemetry.OpenTelemetry.execute(
[:electric, :shapes, :total_shapes],
%{count: num_shapes},
%{stack_id: stack_id}
)
end
Electric.Telemetry.OpenTelemetry.execute(
[:electric, :shapes, :active_shapes],
%{count: Electric.Shapes.ConsumerRegistry.active_consumer_count(stack_id)},
%{stack_id: stack_id}
)
end
def report_write_buffer_size(stack_id, _telemetry_opts) do
alias Electric.ShapeCache.ShapeStatus.ShapeDb
pending_count = ShapeDb.pending_buffer_size(stack_id)
Electric.Telemetry.OpenTelemetry.execute(
[:electric, :shape_db, :write_buffer, :pending_writes],
%{count: pending_count},
%{stack_id: stack_id}
)
end
@min_signed_int8 -2 ** 63
@retained_wal_size_query """
SELECT
(pg_current_wal_lsn() - '0/0' + #{@min_signed_int8})::int8 AS pg_wal_offset,
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)::int8 AS retained_wal_size,
pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)::int8 AS confirmed_flush_lsn_lag
FROM
pg_replication_slots
WHERE
slot_name = $1
"""
@doc false
@spec report_retained_wal_size(Electric.stack_id(), binary(), map()) :: :ok
def report_retained_wal_size(stack_id, slot_name, _telemetry_opts) do
try do
%Postgrex.Result{rows: [[pg_wal_offset, retained_wal_size, confirmed_flush_lsn_lag]]} =
Postgrex.query!(
Electric.Connection.Manager.admin_pool(stack_id),
@retained_wal_size_query,
[slot_name],
timeout: 3_000,
deadline: 3_000
)
# The query above can return `-1` for `confirmed_flush_lsn_lag` which means that Electric
# is caught up with Postgres' replication stream.
# This is a confusing stat if we're measuring in bytes, so use 0 as the bottom limit.
Electric.Telemetry.OpenTelemetry.execute(
[:electric, :postgres, :replication],
%{
# The absolute value of pg_current_wal_lsn() doesn't convey any useful info but by
# plotting its rate of change we can see how fast the WAL is growing.
#
# We shift the absolute value of pg_current_wal_lsn() by -2**63 in the query above
# to make sure it fits inside the signed 64-bit integer type expected by the
# OpenTelemetry Protocol,
pg_wal_offset: pg_wal_offset,
slot_retained_wal_size: retained_wal_size,
slot_confirmed_flush_lsn_lag: max(0, confirmed_flush_lsn_lag)
},
%{stack_id: stack_id}
)
catch
:exit, {:noproc, _} ->
:ok
# catch all errors to not log them as errors, those are reporing issues at best
type, reason ->
Logger.warning(
"Failed to query retained WAL size\nError: #{Exception.format(type, reason)}",
stack_id: stack_id,
slot_name: slot_name
)
end
end
if Code.ensure_loaded?(ElectricTelemetry.DiskUsage) do
def report_disk_usage(stack_id, _telemetry_opts) do
case ElectricTelemetry.DiskUsage.current(stack_id) do
{:ok, usage_bytes, measurement_duration} ->
Electric.Telemetry.OpenTelemetry.execute(
[:electric, :storage, :used],
%{bytes: usage_bytes, measurement_duration: measurement_duration},
%{stack_id: stack_id}
)
:pending ->
:ok
end
end
else
def report_disk_usage(_stack_id, _telemetry_opts) do
:ok
end
end
def report_shape_db_stats(stack_id, _telemetry_opts) do
case Electric.ShapeCache.ShapeStatus.ShapeDb.statistics(stack_id) do
{:ok, stats} ->
Electric.Telemetry.OpenTelemetry.execute(
[:electric, :shape_db, :sqlite],
stats,
%{stack_id: stack_id}
)
_ ->
:ok
end
end
def child_spec(%{stack_telemetry: stack_telemetry}), do: stack_telemetry
if Code.ensure_loaded?(ElectricTelemetry.StackTelemetry) do
def child_spec(config) when is_map(config) do
telemetry_opts =
config.telemetry_opts
|> Keyword.put(:stack_id, config.stack_id)
|> Keyword.put(:storage_dir, config.storage_dir)
# Always enable default periodic measurements in addition to the user-provided ones
|> Keyword.update(
:periodic_measurements,
default_periodic_measurements(config),
&(default_periodic_measurements(config) ++ &1)
)
# Add metrics for the default periodic measurements regardless of whether the
# measurements themselves are occuring.
|> Keyword.update(
:additional_metrics,
default_metrics_from_periodic_measurements(),
&(default_metrics_from_periodic_measurements() ++ &1)
)
{ElectricTelemetry.StackTelemetry, telemetry_opts}
end
defp default_metrics_from_periodic_measurements do
[
Telemetry.Metrics.last_value("electric.shapes.total_shapes.count"),
Telemetry.Metrics.last_value("electric.shapes.active_shapes.count"),
Telemetry.Metrics.last_value("electric.shape_db.write_buffer.pending_writes.count"),
Telemetry.Metrics.last_value("electric.postgres.replication.pg_wal_offset"),
Telemetry.Metrics.last_value("electric.postgres.replication.slot_retained_wal_size",
unit: :byte
),
Telemetry.Metrics.last_value("electric.postgres.replication.slot_confirmed_flush_lsn_lag",
unit: :byte
),
Telemetry.Metrics.last_value("electric.shape_db.sqlite.connections")
]
end
else
def child_spec(_), do: nil
end
end