Last active
October 23, 2015 19:05
-
-
Save joakimk/09ffdf6f47ca63412dc5 to your computer and use it in GitHub Desktop.
Phoenix channel status using an elixir agent. Will only work on a single server.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule BroadcastChannel do | |
use Phoenix.Channel | |
def join(channel, auth_message, socket) do | |
socket = assign(socket, :client_version, auth_message["client_version"]) | |
ClientStats.join(channel, socket.assigns[:client_version]) | |
broadcast_stats | |
{:ok, socket} | |
end | |
def terminate(_reason, socket) do | |
channel = socket.topic | |
ClientStats.leave(channel, socket.assigns[:client_version]) | |
broadcast_stats | |
:ok | |
end | |
defp broadcast_stats do | |
stats = ClientStats.current | |
Endpoint.broadcast("statistics", "all", stats) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule ClientStats do | |
@most_popular_channels_limit 10 | |
def start_link do | |
Agent.start_link(fn -> default_stats end, name: __MODULE__) | |
end | |
def join(channel_name, version) do | |
update_count channel_name, 1 | |
update_version_count version, 1 | |
end | |
def leave(channel_name, version) do | |
update_count channel_name, -1 | |
update_version_count version, -1 | |
end | |
def clear do | |
update fn (stats) -> default_stats end | |
end | |
def current do | |
Agent.get(__MODULE__, fn (stats) -> | |
%{ | |
connected_clients: stats.connected_clients, | |
max_connected_clients: stats.max_connected_clients, | |
client_versions: client_versions_as_array(stats), | |
most_popular_channels: most_popular_channels(stats) | |
} | |
end) | |
end | |
defp update_count(channel_name, count_difference) do | |
update fn (stats) -> | |
active_channels = | |
stats.active_channels | |
|> Map.put(channel_name, current_active_channels_count(stats, channel_name) + count_difference) | |
|> reject_empty | |
|> Enum.into(%{}) | |
stats = stats | |
|> Map.put(:active_channels, active_channels) | |
|> Map.put(:connected_clients, stats.connected_clients + count_difference) | |
stats | |
|> Map.put(:max_connected_clients, max_connected_clients(stats)) | |
end | |
end | |
defp update_version_count(version, count_difference) do | |
update fn (stats) -> | |
current_count = stats.client_versions[version] || 0 | |
client_versions = stats.client_versions | |
|> Map.put(version, current_count + count_difference) | |
|> reject_empty | |
|> Enum.into(%{}) | |
stats = stats | |
|> Map.put(:client_versions, client_versions) | |
stats | |
end | |
end | |
defp max_connected_clients(stats) do | |
if stats.connected_clients > stats.max_connected_clients do | |
stats.connected_clients | |
else | |
stats.max_connected_clients | |
end | |
end | |
defp client_versions_as_array(stats) do | |
Enum.map(stats.client_versions, fn ({version, connected_clients}) -> | |
%{ | |
name: version, | |
connected_clients: connected_clients, | |
} | |
end) | |
|> Enum.sort_by(&(&1.connected_clients)) | |
|> Enum.reverse | |
end | |
defp most_popular_channels(stats) do | |
Enum.map(stats.active_channels, fn ({name, count}) -> | |
%{ | |
name: name, | |
connected_clients: count, | |
} | |
end) | |
|> Enum.sort_by(&(&1.connected_clients)) | |
|> Enum.reverse | |
|> limit_channels | |
end | |
defp reject_empty(channels) do | |
channels | |
|> Enum.drop_while(fn ({name, count}) -> count == 0 end) | |
end | |
defp current_active_channels_count(stats, channel_name) do | |
stats.active_channels[channel_name] || 0 | |
end | |
defp limit_channels(channels) do | |
{ first_group, _rest } = Enum.split(channels, @most_popular_channels_limit) | |
first_group | |
end | |
defp default_stats do | |
%{ | |
connected_clients: 0, | |
max_connected_clients: 0, | |
client_versions: %{}, | |
active_channels: %{} | |
} | |
end | |
defp update(callback) do | |
Agent.update(__MODULE__, callback) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule ClientStatsTest do | |
use ExUnit.Case | |
setup do | |
ClientStats.clear | |
end | |
test "stores stats" do | |
ClientStats.join("test2", "1.0") | |
ClientStats.join("test1", "1.1") | |
ClientStats.join("test2", "1.1") | |
stats = ClientStats.current | |
assert stats == %{ | |
connected_clients: 3, | |
max_connected_clients: 3, | |
client_versions: [ | |
%{ name: "1.1", connected_clients: 2 }, | |
%{ name: "1.0", connected_clients: 1 }, | |
], | |
most_popular_channels: [ | |
%{ name: "test2", connected_clients: 2 }, | |
%{ name: "test1", connected_clients: 1 }, | |
] | |
} | |
end | |
test "removes stats when a client leaves" do | |
ClientStats.join("test", "1.0") | |
ClientStats.join("test", "1.0") | |
ClientStats.leave("test", "1.0") | |
stats = ClientStats.current | |
assert stats == %{ | |
connected_clients: 1, | |
max_connected_clients: 2, | |
client_versions: [ | |
%{ name: "1.0", connected_clients: 1 }, | |
], | |
most_popular_channels: [ | |
%{ name: "test", connected_clients: 1 }, | |
] | |
} | |
ClientStats.leave("test", "1.0") | |
stats = ClientStats.current | |
assert stats == %{ | |
connected_clients: 0, | |
max_connected_clients: 2, | |
client_versions: [], | |
most_popular_channels: [] | |
} | |
end | |
test "limits most_popular_channels to 15 entries" do | |
Enum.each 1..20, fn (number) -> | |
ClientStats.join("test#{number}", "1.0") | |
end | |
stats = ClientStats.current | |
assert stats.connected_clients == 20 | |
assert Enum.count(stats.most_popular_channels) == 10 | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Kind of like how this would read: