perf: reduce traversals, optimize packet field lookups, use has_weather boolean
Remove redundant in-memory sort (DB already orders), shortcut get_packet_field when field found at top level, use pre-computed has_weather boolean instead of field-by-field scan Ultraworked with Sisyphus Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
e02fafc8da
commit
7f814462ed
2 changed files with 43 additions and 37 deletions
|
|
@ -33,29 +33,20 @@ defmodule AprsmeWeb.MapLive.DataBuilder do
|
|||
def build_packet_data_list_from_map(packets_map, is_most_recent, socket) do
|
||||
locale = get_locale(socket)
|
||||
|
||||
# Preload weather callsigns in one batch query instead of N+1 individual queries
|
||||
callsigns =
|
||||
packets_map
|
||||
|> Map.values()
|
||||
|> Enum.map(&display_name/1)
|
||||
|> Enum.uniq()
|
||||
|
||||
weather_set = Aprsme.Packets.weather_callsigns(callsigns)
|
||||
Process.put(:weather_callsigns_cache, weather_set)
|
||||
Aprsme.WeatherCache.cache_weather_callsigns(callsigns)
|
||||
|
||||
# try/after so the cache is cleared even if packet building raises —
|
||||
# otherwise a leftover :weather_callsigns_cache entry would poison
|
||||
# subsequent calls on the same LiveView process.
|
||||
try do
|
||||
packets_map
|
||||
|> Enum.map(fn {_callsign, packet} ->
|
||||
build_packet_data(packet, is_most_recent, locale)
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|> deduplicate_by_callsign_group()
|
||||
after
|
||||
Process.delete(:weather_callsigns_cache)
|
||||
end
|
||||
packets_map
|
||||
|> Enum.map(fn {_callsign, packet} ->
|
||||
build_packet_data(packet, is_most_recent, locale)
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|> deduplicate_by_callsign_group()
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
@ -175,7 +166,6 @@ defmodule AprsmeWeb.MapLive.DataBuilder do
|
|||
"""
|
||||
@spec build_packet_data_list(list()) :: list()
|
||||
def build_packet_data_list(historical_packets) do
|
||||
# Include weather data in initial grouping to avoid separate query
|
||||
# Group by display name so objects/items are grouped by their name, not sender
|
||||
grouped_packets =
|
||||
Enum.group_by(historical_packets, fn packet ->
|
||||
|
|
@ -190,10 +180,7 @@ defmodule AprsmeWeb.MapLive.DataBuilder do
|
|||
# each group, so the JS can iterate directly for trail drawing without re-sorting.
|
||||
grouped_packets
|
||||
|> Enum.flat_map(fn {callsign, packets} ->
|
||||
# Sort by received_at to find most recent
|
||||
sorted_packets = Enum.sort_by(packets, &get_packet_received_at(&1), {:desc, DateTime})
|
||||
|
||||
case sorted_packets do
|
||||
case packets do
|
||||
[] ->
|
||||
[]
|
||||
|
||||
|
|
@ -548,12 +535,14 @@ defmodule AprsmeWeb.MapLive.DataBuilder do
|
|||
|
||||
@spec has_weather_packets?(String.t()) :: boolean()
|
||||
defp has_weather_packets?(callsign) when is_binary(callsign) do
|
||||
case Process.get(:weather_callsigns_cache) do
|
||||
nil -> Aprsme.Packets.has_weather_packets?(callsign)
|
||||
weather_set -> MapSet.member?(weather_set, String.upcase(String.trim(callsign)))
|
||||
case Aprsme.WeatherCache.weather_callsign?(callsign) do
|
||||
:unknown ->
|
||||
Aprsme.WeatherCache.cache_weather_callsigns([callsign])
|
||||
Aprsme.WeatherCache.weather_callsign?(callsign) == true
|
||||
|
||||
result when is_boolean(result) ->
|
||||
result
|
||||
end
|
||||
rescue
|
||||
_ -> false
|
||||
end
|
||||
|
||||
@spec convert_tuples_to_strings(any()) :: any()
|
||||
|
|
|
|||
|
|
@ -94,13 +94,21 @@ defmodule AprsmeWeb.Live.Shared.PacketUtils do
|
|||
|
||||
@doc """
|
||||
Check if packet contains weather data.
|
||||
Uses the pre-computed has_weather boolean when available (DB structs),
|
||||
falls back to field-by-field scan for live PubSub payloads.
|
||||
"""
|
||||
@spec has_weather_data?(map()) :: boolean()
|
||||
def has_weather_data?(packet) do
|
||||
Enum.any?(Aprsme.EncodingUtils.weather_fields(), fn field ->
|
||||
value = get_packet_field(packet, field, nil)
|
||||
not is_nil(value)
|
||||
end)
|
||||
case Map.get(packet, :has_weather) || Map.get(packet, "has_weather") do
|
||||
nil ->
|
||||
Enum.any?(Aprsme.EncodingUtils.weather_fields(), fn field ->
|
||||
value = get_packet_field(packet, field, nil)
|
||||
not is_nil(value)
|
||||
end)
|
||||
|
||||
bool when is_boolean(bool) ->
|
||||
bool
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
@ -126,17 +134,26 @@ defmodule AprsmeWeb.Live.Shared.PacketUtils do
|
|||
|
||||
@doc """
|
||||
Get packet field value with fallback and default.
|
||||
Checks both atom and string keys, and data_extended field.
|
||||
"""
|
||||
@spec get_packet_field(map(), atom() | String.t(), any()) :: any()
|
||||
def get_packet_field(packet, field, default \\ nil) do
|
||||
data_extended = Map.get(packet, :data_extended, Map.get(packet, "data_extended", %{})) || %{}
|
||||
case Map.get(packet, field) do
|
||||
nil ->
|
||||
case Map.get(packet, to_string(field)) do
|
||||
nil ->
|
||||
de =
|
||||
Map.get(packet, :data_extended) ||
|
||||
Map.get(packet, "data_extended", %{}) || %{}
|
||||
|
||||
Map.get(packet, field) ||
|
||||
Map.get(packet, to_string(field)) ||
|
||||
Map.get(data_extended, field) ||
|
||||
Map.get(data_extended, to_string(field)) ||
|
||||
default
|
||||
Map.get(de, field) || Map.get(de, to_string(field)) || default
|
||||
|
||||
val ->
|
||||
val
|
||||
end
|
||||
|
||||
val ->
|
||||
val
|
||||
end
|
||||
end
|
||||
|
||||
# Private helper functions
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue