Skip to content

Commit bc36fd6

Browse files
Merge pull request #261 from permaweb/feat/cacheviz2
feat: added cache visualizer v2
2 parents b3eee02 + 04294a6 commit bc36fd6

File tree

6 files changed

+4342
-147
lines changed

6 files changed

+4342
-147
lines changed

src/dev_hyperbuddy.erl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ info() ->
1010
routes => #{
1111
<<"index">> => <<"index.html">>,
1212
<<"console">> => <<"console.html">>,
13+
<<"graph">> => <<"graph.html">>,
1314
<<"styles.css">> => <<"styles.css">>,
1415
<<"metrics.js">> => <<"metrics.js">>,
1516
<<"devices.js">> => <<"devices.js">>,
1617
<<"utils.js">> => <<"utils.js">>,
17-
<<"main.js">> => <<"main.js">>
18+
<<"main.js">> => <<"main.js">>,
19+
<<"graph.js">> => <<"graph.js">>
1820
}
1921
}.
2022

@@ -47,6 +49,7 @@ format(Base, _, _) ->
4749
%% listed in the `routes' field of the `info/0' return value.
4850
serve(<<"keys">>, M1, _M2, _Opts) -> dev_message:keys(M1);
4951
serve(<<"set">>, M1, M2, Opts) -> dev_message:set(M1, M2, Opts);
52+
serve(<<"graph-data">>, _, _, Opts) -> hb_cache_render:get_graph_data(Opts);
5053
serve(Key, _, _, _) ->
5154
?event({hyperbuddy_serving, Key}),
5255
case maps:get(Key, maps:get(routes, info(), no_routes), undefined) of

src/hb_cache_render.erl

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
% Preparing data for testing
55
-export([prepare_unsigned_data/0, prepare_signed_data/0,
66
prepare_deeply_nested_complex_message/0]).
7+
-export([cache_path_to_graph/3, get_graph_data/1]).
78
-include("include/hb.hrl").
89

910
%% @doc Render the given Key into svg
@@ -200,6 +201,100 @@ collect_output(Port, Acc) ->
200201
{error, timeout}
201202
end.
202203

204+
%% @doc Get graph data for the Three.js visualization
205+
get_graph_data(Opts) ->
206+
% Get the store from options
207+
Store = hb_opts:get(store, no_viable_store, Opts),
208+
209+
% Try to generate graph using hb_cache_render
210+
Graph = try
211+
% Use hb_cache_render to build the graph
212+
{ok, Keys} = hb_store:list(Store, "/"),
213+
cache_path_to_graph(Store, #{}, Keys)
214+
catch
215+
Error:Reason:Stack ->
216+
?event({hyperbuddy_graph_error, Error, Reason, Stack}),
217+
#{nodes => #{}, arcs => #{}, visited => #{}}
218+
end,
219+
220+
% Extract nodes and links for the visualization
221+
NodesMap = maps:get(nodes, Graph, #{}),
222+
ArcsMap = maps:get(arcs, Graph, #{}),
223+
224+
% Limit to top 500 nodes if there are too many
225+
NodesList =
226+
case maps:size(NodesMap) > 50000 of
227+
true ->
228+
% Take a subset of nodes
229+
{ReducedNodes, _} = lists:split(
230+
500,
231+
maps:to_list(NodesMap)
232+
),
233+
ReducedNodes;
234+
false ->
235+
maps:to_list(NodesMap)
236+
end,
237+
238+
% Get node IDs for filtering links
239+
NodeIds = [ID || {ID, _} <- NodesList],
240+
241+
% Convert to JSON format for web visualization
242+
Nodes = [
243+
#{
244+
<<"id">> => ID,
245+
<<"label">> => get_label(hb_util:bin(ID)),
246+
<<"type">> => get_node_type(Color)
247+
}
248+
|| {ID, {_, Color}} <- NodesList
249+
],
250+
251+
% Filter links to only include those between nodes we're showing
252+
FilteredLinks = [
253+
{From, To, Label}
254+
|| {From, To, Label} <- maps:keys(ArcsMap),
255+
lists:member(From, NodeIds) andalso lists:member(To, NodeIds)
256+
],
257+
258+
Links = [
259+
#{
260+
<<"source">> => From,
261+
<<"target">> => To,
262+
<<"label">> => Label
263+
}
264+
|| {From, To, Label} <- FilteredLinks
265+
],
266+
267+
% Return the JSON data
268+
JsonData = hb_json:encode(#{
269+
<<"nodes">> => Nodes,
270+
<<"links">> => Links
271+
}),
272+
273+
{ok, #{
274+
<<"body">> => JsonData,
275+
<<"content-type">> => <<"application/json">>
276+
}}.
277+
278+
%% @doc Convert node color from hb_cache_render to node type for visualization
279+
get_node_type(Color) ->
280+
case Color of
281+
"lightblue" -> <<"simple">>;
282+
"lightcoral" -> <<"composite">>;
283+
_ -> <<"unknown">>
284+
end.
285+
286+
%% @doc Extract a readable label from a path
287+
get_label(Path) ->
288+
case binary:split(Path, <<"/">>, [global]) of
289+
[] -> Path;
290+
Parts ->
291+
FilteredParts = [P || P <- Parts, P /= <<>>],
292+
case FilteredParts of
293+
[] -> Path;
294+
_ -> lists:last(FilteredParts)
295+
end
296+
end.
297+
203298
% Test data preparation functions
204299
prepare_unsigned_data() ->
205300
Opts = #{

0 commit comments

Comments
 (0)