From eddd744f8dbbd3f25399d6b5d95a10f4046d9578 Mon Sep 17 00:00:00 2001 From: Cort Fritz Date: Tue, 24 Dec 2024 16:48:18 -0800 Subject: [PATCH] remove accidentally added bench test dir --- bench/doc_stress.exs | 254 ---------------------------------- bench/observer_stress.exs | 238 ------------------------------- bench/undo_manager_bench.exs | 61 -------- bench/undo_manager_stress.exs | 177 ----------------------- 4 files changed, 730 deletions(-) delete mode 100644 bench/doc_stress.exs delete mode 100644 bench/observer_stress.exs delete mode 100644 bench/undo_manager_bench.exs delete mode 100644 bench/undo_manager_stress.exs diff --git a/bench/doc_stress.exs b/bench/doc_stress.exs deleted file mode 100644 index 9b8885a..0000000 --- a/bench/doc_stress.exs +++ /dev/null @@ -1,254 +0,0 @@ -# Run with: mix run bench/doc_stress.exs - -defmodule DocStress do - @num_concurrent_actors 50 # How many actors to keep active at once - @total_actors 1000 # Total number of actors to create over the test - @actor_lifetime_ms 5000 # How long each actor lives - @test_duration_ms 30_000 # Total test duration - - def run do - # Create a shared document - doc = Yex.Doc.new() - text = Yex.Doc.get_text(doc, "text") - map = Yex.Doc.get_map(doc, "map") - array = Yex.Doc.get_array(doc, "array") - xml = Yex.Doc.get_xml_fragment(doc, "xml") - - # Initialize with some content - Yex.Text.insert(text, 0, String.duplicate("x", 200_000)) - - IO.puts("\nStarting doc stress test:") - IO.puts("- #{@num_concurrent_actors} concurrent actors") - IO.puts("- #{@total_actors} total actors") - IO.puts("- #{@actor_lifetime_ms}ms actor lifetime") - IO.puts("- #{@test_duration_ms}ms test duration") - - # Start progress indicator - spawn_link(fn -> progress_indicator(@test_duration_ms) end) - - # Start actor spawner - spawn_link(fn -> actor_spawner(doc, text, map, array, xml) end) - - # Run for specified duration - Process.sleep(@test_duration_ms) - - # Print results - IO.puts("\nStress test completed") - - # Give time for final metrics collection - Process.sleep(1000) - end - - defp actor_spawner(doc, text, map, array, xml) do - actor_spawner(doc, text, map, array, xml, 0, MapSet.new()) - end - - defp actor_spawner(doc, text, map, array, xml, count, active_pids) when count < @total_actors do - # Remove any finished actors - active_pids = - active_pids - |> Enum.filter(&Process.alive?/1) - |> MapSet.new() - - # Spawn new actors if we're below the concurrent limit - active_pids = - if MapSet.size(active_pids) < @num_concurrent_actors do - pid = spawn_actor(doc, text, map, array, xml, count) - MapSet.put(active_pids, pid) - else - active_pids - end - - Process.sleep(100) # Control spawn rate - actor_spawner(doc, text, map, array, xml, count + 1, active_pids) - end - - defp actor_spawner(_doc, _text, _map, _array, _xml, count, _active_pids) do - MemoryMetrics.record_final_actors(count) - end - - defp spawn_actor(doc, text, map, array, xml, id) do - spawn_link(fn -> - start_time = System.monotonic_time(:millisecond) - - try do - # Keep doc reference alive while working with shared types - _doc_ref = doc - - # Do some work with all types - Enum.each(1..10, fn _ -> - operation = random_operation() - - case operation do - :text -> - pos = :rand.uniform(200_000) - 1 - content = random_string(1..10) - Yex.Text.insert(text, pos, content) - - :map -> - key = "key_#{:rand.uniform(1000)}" - value = "value_#{:rand.uniform(1000)}" - Yex.Map.set(map, key, value) - - :array -> - value = "item_#{:rand.uniform(1000)}" - Yex.Array.push(array, value) - - :xml -> - tag_num = :rand.uniform(100) - content = "content" - Yex.XmlFragment.push(xml, Yex.XmlTextPrelim.from(content)) - end - end) - - # Record successful operations - MemoryMetrics.record_operations_completed() - - # Live for specified duration - remaining_time = @actor_lifetime_ms - (System.monotonic_time(:millisecond) - start_time) - if remaining_time > 0, do: Process.sleep(remaining_time) - - rescue - e -> - IO.puts("\nActor #{id} error: #{inspect(e)}") - MemoryMetrics.record_error() - end - end) - end - - defp random_operation do - case :rand.uniform(100) do - x when x <= 40 -> :text # 40% chance - x when x <= 70 -> :map # 30% chance - x when x <= 90 -> :array # 20% chance - _ -> :xml # 10% chance - end - end - - defp random_string(range) do - length = :rand.uniform(Enum.max(range)) - :crypto.strong_rand_bytes(length) - |> Base.encode64() - |> binary_part(0, length) - end - - defp progress_indicator(total_ms) do - interval = 1000 # Update every second - segments = trunc(total_ms / interval) - - Enum.reduce(1..segments, 0, fn i, _ -> - percent = Float.round(i / segments * 100, 1) - memory = :erlang.memory() - - clear_line() - IO.write("\rProgress: [#{String.duplicate("=", trunc(i/segments * 40))}#{String.duplicate(" ", 40 - trunc(i/segments * 40))}] #{percent}%") - IO.write(" | Memory: #{format_bytes(memory[:total])}") - - MemoryMetrics.record_memory_point(memory) - Process.sleep(interval) - i - end) - - clear_line() - IO.write("\rProgress: [#{String.duplicate("=", 40)}] 100%\n") - end - - defp clear_line, do: IO.write("\r#{String.duplicate(" ", 120)}") - - defp format_bytes(bytes) when bytes < 1024, do: "#{bytes}B" - defp format_bytes(bytes) when bytes < 1024 * 1024, do: "#{Float.round(bytes / 1024, 2)}KB" - defp format_bytes(bytes), do: "#{Float.round(bytes / 1024 / 1024, 2)}MB" -end - -defmodule MemoryMetrics do - use GenServer - - def start_link do - GenServer.start_link(__MODULE__, %{}, name: __MODULE__) - end - - def init(_) do - :ets.new(:memory_metrics, [:named_table, :public]) - {:ok, %{ - start_time: System.monotonic_time(:millisecond), - operations_completed: 0, - errors: 0, - final_actors: 0 - }} - end - - def record_memory_point(memory) do - time = System.monotonic_time(:millisecond) - :ets.insert(:memory_metrics, {{:memory, time}, memory}) - end - - def record_operations_completed do - GenServer.cast(__MODULE__, :operation_completed) - end - - def record_error do - GenServer.cast(__MODULE__, :error) - end - - def record_final_actors(count) do - GenServer.cast(__MODULE__, {:final_actors, count}) - end - - def print_metrics do - state = GenServer.call(__MODULE__, :get_state) - memory_points = :ets.match_object(:memory_metrics, {{:memory, :_}, :_}) - - IO.puts("\nOperation Metrics:") - IO.puts("================") - IO.puts("Total actors created: #{state.final_actors}") - IO.puts("Operations completed: #{state.operations_completed}") - IO.puts("Errors: #{state.errors}") - - case memory_points do - [] -> - IO.puts("No memory data collected") - - points -> - {min_memory, max_memory, avg_memory} = analyze_memory(points) - IO.puts("\nMemory Usage:") - IO.puts("Min: #{format_bytes(min_memory)}") - IO.puts("Max: #{format_bytes(max_memory)}") - IO.puts("Avg: #{format_bytes(trunc(avg_memory))}") - end - end - - defp analyze_memory(points) do - memories = Enum.map(points, fn {{:memory, _}, memory} -> memory[:total] end) - { - Enum.min(memories), - Enum.max(memories), - Enum.sum(memories) / length(memories) - } - end - - defp format_bytes(bytes) when bytes < 1024, do: "#{bytes}B" - defp format_bytes(bytes) when bytes < 1024 * 1024, do: "#{Float.round(bytes / 1024, 2)}KB" - defp format_bytes(bytes), do: "#{Float.round(bytes / 1024 / 1024, 2)}MB" - - # Server callbacks - def handle_cast(:operation_completed, state) do - {:noreply, %{state | operations_completed: state.operations_completed + 1}} - end - - def handle_cast(:error, state) do - {:noreply, %{state | errors: state.errors + 1}} - end - - def handle_cast({:final_actors, count}, state) do - {:noreply, Map.put(state, :final_actors, count)} - end - - def handle_call(:get_state, _from, state) do - {:reply, state, state} - end -end - -# Run the stress test -{:ok, _pid} = MemoryMetrics.start_link() -DocStress.run() -MemoryMetrics.print_metrics() diff --git a/bench/observer_stress.exs b/bench/observer_stress.exs deleted file mode 100644 index f224ca8..0000000 --- a/bench/observer_stress.exs +++ /dev/null @@ -1,238 +0,0 @@ -# Run with: mix run bench/observer_stress.exs - -defmodule ObserverStress do - @num_concurrent_actors 50 # How many actors to keep active at once - @total_actors 1000 # Total number of actors to create over the test - @actor_lifetime_ms 5000 # How long each actor lives - @test_duration_ms 30_000 # Total test duration - - def run do - # Create a shared document - doc = Yex.Doc.new() - text = Yex.Doc.get_text(doc, "text") - - IO.puts("\nStarting observer stress test:") - IO.puts("- #{@num_concurrent_actors} concurrent actors") - IO.puts("- #{@total_actors} total actors") - IO.puts("- #{@actor_lifetime_ms}ms actor lifetime") - IO.puts("- #{@test_duration_ms}ms test duration") - - # Start progress indicator - spawn_link(fn -> progress_indicator(@test_duration_ms) end) - - # Start actor spawner - spawn_link(fn -> actor_spawner(doc, text) end) - - # Run for specified duration - Process.sleep(@test_duration_ms) - - # Print results - IO.puts("\nStress test completed") - - # Give time for final metrics collection - Process.sleep(1000) - end - - defp actor_spawner(doc, text) do - actor_spawner(doc, text, 0, MapSet.new()) - end - - defp actor_spawner(doc, text, count, active_pids) when count < @total_actors do - # Remove any finished actors - active_pids = - active_pids - |> Enum.filter(&Process.alive?/1) - |> MapSet.new() - - # Spawn new actors if we're below the concurrent limit - active_pids = - if MapSet.size(active_pids) < @num_concurrent_actors do - pid = spawn_actor(doc, text, count) - MapSet.put(active_pids, pid) - else - active_pids - end - - Process.sleep(100) # Control spawn rate - actor_spawner(doc, text, count + 1, active_pids) - end - - defp actor_spawner(_doc, _text, count, _active_pids) do - MemoryMetrics.record_final_actors(count) - end - - defp spawn_actor(doc, text, id) do - spawn_link(fn -> - start_time = System.monotonic_time(:millisecond) - - try do - {:ok, manager} = Yex.UndoManager.new(doc, text) - - # Add multiple observers - {:ok, manager} = Yex.UndoManager.on_item_added(manager, fn _event -> - %{actor_id: id, type: :added} - end) - - {:ok, manager} = Yex.UndoManager.on_item_updated(manager, fn _event -> - %{actor_id: id, type: :updated} - end) - - {:ok, manager} = Yex.UndoManager.on_item_popped(manager, fn _id, _event -> - %{actor_id: id, type: :popped} - end) - - # Record successful observer creation - MemoryMetrics.record_observer_created() - - # Do some work - Enum.each(1..5, fn _ -> - Yex.Text.insert(text, 0, "test") - Yex.UndoManager.undo(manager) - Process.sleep(:rand.uniform(500)) - end) - - # Live for specified duration - remaining_time = @actor_lifetime_ms - (System.monotonic_time(:millisecond) - start_time) - if remaining_time > 0, do: Process.sleep(remaining_time) - - # Record successful cleanup - MemoryMetrics.record_observer_cleaned() - - rescue - e -> - IO.puts("\nActor #{id} error: #{inspect(e)}") - MemoryMetrics.record_error() - end - end) - end - - defp progress_indicator(total_ms) do - interval = 1000 # Update every second - segments = trunc(total_ms / interval) - - Enum.reduce(1..segments, 0, fn i, _ -> - percent = Float.round(i / segments * 100, 1) - memory = :erlang.memory() - - clear_line() - IO.write("\rProgress: [#{String.duplicate("=", trunc(i/segments * 40))}#{String.duplicate(" ", 40 - trunc(i/segments * 40))}] #{percent}%") - IO.write(" | Memory: #{format_bytes(memory[:total])}") - - MemoryMetrics.record_memory_point(memory) - Process.sleep(interval) - i - end) - - clear_line() - IO.write("\rProgress: [#{String.duplicate("=", 40)}] 100%\n") - end - - defp clear_line, do: IO.write("\r#{String.duplicate(" ", 120)}") - - defp format_bytes(bytes) when bytes < 1024, do: "#{bytes}B" - defp format_bytes(bytes) when bytes < 1024 * 1024, do: "#{Float.round(bytes / 1024, 2)}KB" - defp format_bytes(bytes), do: "#{Float.round(bytes / 1024 / 1024, 2)}MB" -end - -defmodule MemoryMetrics do - use GenServer - - def start_link do - GenServer.start_link(__MODULE__, %{}, name: __MODULE__) - end - - def init(_) do - :ets.new(:memory_metrics, [:named_table, :public]) - {:ok, %{ - start_time: System.monotonic_time(:millisecond), - observers_created: 0, - observers_cleaned: 0, - errors: 0, - final_actors: 0 - }} - end - - def record_memory_point(memory) do - time = System.monotonic_time(:millisecond) - :ets.insert(:memory_metrics, {{:memory, time}, memory}) - end - - def record_observer_created do - GenServer.cast(__MODULE__, :observer_created) - end - - def record_observer_cleaned do - GenServer.cast(__MODULE__, :observer_cleaned) - end - - def record_error do - GenServer.cast(__MODULE__, :error) - end - - def record_final_actors(count) do - GenServer.cast(__MODULE__, {:final_actors, count}) - end - - def print_metrics do - state = GenServer.call(__MODULE__, :get_state) - memory_points = :ets.match_object(:memory_metrics, {{:memory, :_}, :_}) - - IO.puts("\nMemory Metrics:") - IO.puts("==============") - IO.puts("Total actors created: #{state.final_actors || 0}") - IO.puts("Observers created: #{state.observers_created}") - IO.puts("Observers cleaned: #{state.observers_cleaned}") - IO.puts("Errors: #{state.errors}") - - case memory_points do - [] -> - IO.puts("No memory data collected") - - points -> - {min_memory, max_memory, avg_memory} = analyze_memory(points) - IO.puts("\nMemory Usage:") - IO.puts("Min: #{format_bytes(min_memory)}") - IO.puts("Max: #{format_bytes(max_memory)}") - IO.puts("Avg: #{format_bytes(trunc(avg_memory))}") - end - end - - defp analyze_memory(points) do - memories = Enum.map(points, fn {{:memory, _}, memory} -> memory[:total] end) - { - Enum.min(memories), - Enum.max(memories), - Enum.sum(memories) / length(memories) - } - end - - defp format_bytes(bytes) when bytes < 1024, do: "#{bytes}B" - defp format_bytes(bytes) when bytes < 1024 * 1024, do: "#{Float.round(bytes / 1024, 2)}KB" - defp format_bytes(bytes), do: "#{Float.round(bytes / 1024 / 1024, 2)}MB" - - # Server callbacks - def handle_cast(:observer_created, state) do - {:noreply, %{state | observers_created: state.observers_created + 1}} - end - - def handle_cast(:observer_cleaned, state) do - {:noreply, %{state | observers_cleaned: state.observers_cleaned + 1}} - end - - def handle_cast(:error, state) do - {:noreply, %{state | errors: state.errors + 1}} - end - - def handle_cast({:final_actors, count}, state) do - {:noreply, Map.put(state, :final_actors, count)} - end - - def handle_call(:get_state, _from, state) do - {:reply, state, state} - end -end - -# Run the stress test -{:ok, _pid} = MemoryMetrics.start_link() -ObserverStress.run() -MemoryMetrics.print_metrics() diff --git a/bench/undo_manager_bench.exs b/bench/undo_manager_bench.exs deleted file mode 100644 index 1647b53..0000000 --- a/bench/undo_manager_bench.exs +++ /dev/null @@ -1,61 +0,0 @@ -# Run with: mix run bench/undo_manager_bench.exs - -doc_setup = fn _ -> - doc = Yex.Doc.new() - text = Yex.Doc.get_text(doc, "mytext") - {:ok, manager} = Yex.UndoManager.new(doc, text) - {doc, text, manager} -end - -Benchee.run( - %{ - "single operation undo/redo" => fn input -> - {_doc, text, manager} = input - Yex.Text.insert(text, 0, "Hello") - Yex.UndoManager.undo(manager) - Yex.UndoManager.redo(manager) - end, - - "multiple operations batch" => fn input -> - {_doc, text, manager} = input - Yex.Text.insert(text, 0, "Hello") - Yex.Text.insert(text, 5, " World") - Yex.Text.insert(text, 11, "!") - Yex.UndoManager.undo(manager) - end, - - "multiple operations separate" => fn input -> - {_doc, text, manager} = input - Yex.Text.insert(text, 0, "Hello") - Yex.UndoManager.stop_capturing(manager) - Yex.Text.insert(text, 5, " World") - Yex.UndoManager.stop_capturing(manager) - Yex.Text.insert(text, 11, "!") - Yex.UndoManager.undo(manager) - Yex.UndoManager.undo(manager) - Yex.UndoManager.undo(manager) - end, - - "observer callbacks" => fn input -> - {_doc, text, manager} = input - {:ok, manager} = Yex.UndoManager.on_item_added(manager, fn _event -> %{} end) - {:ok, manager} = Yex.UndoManager.on_item_updated(manager, fn _event -> nil end) - {:ok, manager} = Yex.UndoManager.on_item_popped(manager, fn _id, _event -> nil end) - Yex.Text.insert(text, 0, "Test") - Yex.UndoManager.undo(manager) - end, - - "scope expansion" => fn input -> - {doc, text, manager} = input - array = Yex.Doc.get_array(doc, "myarray") - Yex.UndoManager.expand_scope(manager, array) - Yex.Text.insert(text, 0, "Hello") - Yex.Array.push(array, "World") - Yex.UndoManager.undo(manager) - end - }, - before_each: doc_setup, - time: 5, - memory_time: 2, - formatters: [Benchee.Formatters.Console] -) diff --git a/bench/undo_manager_stress.exs b/bench/undo_manager_stress.exs deleted file mode 100644 index a6f7656..0000000 --- a/bench/undo_manager_stress.exs +++ /dev/null @@ -1,177 +0,0 @@ -# Run with: mix run bench/undo_manager_stress.exs - -defmodule UndoManagerStress do - @doc_size 200_000 - @num_actors 10 - @test_duration_ms 30_000 # 30 seconds - - def run do - # Create a shared document - doc = Yex.Doc.new() - text = Yex.Doc.get_text(doc, "text") - map = Yex.Doc.get_map(doc, "map") - array = Yex.Doc.get_array(doc, "array") - xml = Yex.Doc.get_xml_fragment(doc, "xml") - - # Initialize with some content - Yex.Text.insert(text, 0, String.duplicate("x", @doc_size)) - - IO.puts("\nStarting stress test with #{@num_actors} actors for #{@test_duration_ms/1000} seconds...") - - # Start progress indicator - spawn_link(fn -> progress_indicator(@test_duration_ms) end) - - # Start actors - actors = for i <- 1..@num_actors do - {:ok, manager} = Yex.UndoManager.new(doc, text) - # Expand scope to include all types - Yex.UndoManager.expand_scope(manager, map) - Yex.UndoManager.expand_scope(manager, array) - Yex.UndoManager.expand_scope(manager, xml) - - spawn_link(fn -> actor_loop(i, doc, text, map, array, xml, manager) end) - end - - # Run for specified duration - Process.sleep(@test_duration_ms) - - # Stop actors - Enum.each(actors, &Process.exit(&1, :normal)) - - # Print results - IO.puts("\nStress test completed") - end - - defp progress_indicator(total_ms) do - interval = 1000 # Update every second - segments = trunc(total_ms / interval) - - Enum.reduce(1..segments, 0, fn i, _ -> - percent = Float.round(i / segments * 100, 1) - clear_line() - IO.write("\rProgress: [#{String.duplicate("=", trunc(i/segments * 40))}#{String.duplicate(" ", 40 - trunc(i/segments * 40))}] #{percent}%") - Process.sleep(interval) - i - end) - - clear_line() - IO.write("\rProgress: [#{String.duplicate("=", 40)}] 100%\n") - end - - defp clear_line, do: IO.write("\r#{String.duplicate(" ", 80)}") - - defp actor_loop(id, doc, text, map, array, xml, manager) do - operation = random_operation() - - try do - start_time = System.monotonic_time(:millisecond) - - result = case operation do - :text -> - pos = :rand.uniform(@doc_size) - 1 - content = random_string(1..10) - Yex.Text.insert(text, pos, content) - - :map -> - key = "key_#{:rand.uniform(1000)}" - value = "value_#{:rand.uniform(1000)}" - Yex.Map.set(map, key, value) - - :array -> - value = "item_#{:rand.uniform(1000)}" - Yex.Array.push(array, value) - - :xml -> - tag_num = :rand.uniform(100) - content = "content" - Yex.XmlFragment.push(xml, Yex.XmlTextPrelim.from(content)) - - :undo -> - Yex.UndoManager.undo(manager) - - :redo -> - Yex.UndoManager.redo(manager) - end - - end_time = System.monotonic_time(:millisecond) - duration = end_time - start_time - - # Record metrics for successful operations - Metrics.record_operation(operation, duration) - result - - rescue - e -> - IO.puts("\nActor #{id} error on #{operation}: #{inspect(e)}") - Metrics.record_error(operation) - end - - # Random pause between operations (100-500ms) - Process.sleep(:rand.uniform(400) + 100) - actor_loop(id, doc, text, map, array, xml, manager) - end - - defp random_operation do - case :rand.uniform(100) do - x when x <= 7 -> :undo # 7% chance - x when x <= 10 -> :redo # 3% chance - x when x <= 50 -> :text # 40% chance - x when x <= 70 -> :map # 20% chance - x when x <= 90 -> :array # 20% chance - _ -> :xml # 10% chance - end - end - - defp random_string(range) do - length = :rand.uniform(Enum.max(range)) - :crypto.strong_rand_bytes(length) - |> Base.encode64() - |> binary_part(0, length) - end -end - -# Add some basic metrics collection -defmodule Metrics do - use GenServer - - def start_link do - GenServer.start_link(__MODULE__, %{}, name: __MODULE__) - end - - def init(_) do - :ets.new(:operation_metrics, [:named_table, :public]) - :ets.new(:error_metrics, [:named_table, :public]) - {:ok, %{start_time: System.monotonic_time(:millisecond)}} - end - - def record_operation(operation, duration_ms) do - :ets.update_counter(:operation_metrics, operation, {2, 1}, {operation, 0, 0, 0}) - :ets.update_counter(:operation_metrics, operation, {3, duration_ms}, {operation, 0, 0, 0}) - :ets.update_counter(:operation_metrics, operation, {4, 1}, {operation, 0, 0, duration_ms}) - end - - def record_error(operation) do - :ets.update_counter(:error_metrics, operation, {2, 1}, {operation, 0}) - end - - def print_metrics do - IO.puts("\nOperation Metrics:") - IO.puts("================") - - :ets.tab2list(:operation_metrics) - |> Enum.sort() - |> Enum.each(fn {op, count, total_ms, _} -> - avg_ms = if count > 0, do: Float.round(total_ms / count, 2), else: 0 - errors = case :ets.lookup(:error_metrics, op) do - [{_, error_count}] -> error_count - [] -> 0 - end - IO.puts("#{op}: #{count} operations, avg #{avg_ms}ms per operation, #{errors} errors") - end) - end -end - -# Run the stress test -{:ok, _pid} = Metrics.start_link() -UndoManagerStress.run() -Metrics.print_metrics()