• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nshkrdotcom / ElixirScope / 98c7bc70353732b05d64b194bff557eda4ee7949

29 May 2025 05:24AM UTC coverage: 58.208% (-0.6%) from 58.763%
98c7bc70353732b05d64b194bff557eda4ee7949

push

github

NSHkr
fix failing pattern test

6042 of 10380 relevant lines covered (58.21%)

3220.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

59.74
/lib/elixir_scope/ast_repository/memory_manager.ex
1
defmodule ElixirScope.ASTRepository.MemoryManager do
2
  @moduledoc """
3
  Main memory management coordinator for the Enhanced AST Repository.
4

5
  Orchestrates memory monitoring, cleanup, compression, and caching
6
  strategies to handle production-scale projects with 1000+ modules.
7

8
  ## Features
9

10
  - **Memory Monitoring**: Real-time tracking of repository memory usage
11
  - **Intelligent Cleanup**: Remove stale and unused AST data
12
  - **Data Compression**: Compress infrequently accessed analysis data
13
  - **LRU Caching**: Least Recently Used cache for query optimization
14
  - **Memory Pressure Handling**: Multi-level response to memory constraints
15

16
  ## Performance Targets
17

18
  - Memory usage: <500MB for 1000 modules
19
  - Query response: <100ms for 95th percentile
20
  - Cache hit ratio: >80% for repeated queries
21
  - Memory cleanup: <10ms per cleanup cycle
22

23
  ## Examples
24

25
      # Start memory monitoring
26
      {:ok, _pid} = MemoryManager.start_link()
27

28
      # Monitor memory usage
29
      {:ok, stats} = MemoryManager.monitor_memory_usage()
30

31
      # Cleanup unused data
32
      :ok = MemoryManager.cleanup_unused_data(max_age: 3600)
33
  """
34

35
  use GenServer
36
  require Logger
37

38
  alias ElixirScope.ASTRepository.MemoryManager.{
39
    Monitor,
40
    Cleaner,
41
    Compressor,
42
    CacheManager,
43
    PressureHandler
44
  }
45

46
  # Configuration
47
  @memory_check_interval 30_000      # 30 seconds
48
  @cleanup_interval 300_000          # 5 minutes
49
  @compression_interval 600_000      # 10 minutes
50

51
  defstruct [
52
    :memory_stats,
53
    :cache_stats,
54
    :cleanup_stats,
55
    :compression_stats,
56
    :pressure_level,
57
    :last_cleanup,
58
    :last_compression,
59
    :monitoring_enabled
60
  ]
61

62
  @type memory_stats :: Monitor.memory_stats()
63
  @type cache_stats :: CacheManager.cache_stats()
64
  @type cleanup_stats :: Cleaner.cleanup_stats()
65
  @type compression_stats :: Compressor.compression_stats()
66

67
  # GenServer API
68

69
  def start_link(opts \\ []) do
70
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
2✔
71
  end
72

73
  def init(opts) do
74
    # Schedule periodic tasks
75
    schedule_memory_check()
2✔
76
    schedule_cleanup()
2✔
77
    schedule_compression()
2✔
78

79
    state = %__MODULE__{
2✔
80
      memory_stats: %{},
81
      cache_stats: CacheManager.get_initial_stats(),
82
      cleanup_stats: Cleaner.get_initial_stats(),
83
      compression_stats: Compressor.get_initial_stats(),
84
      pressure_level: :normal,
85
      last_cleanup: System.monotonic_time(:millisecond),
86
      last_compression: System.monotonic_time(:millisecond),
87
      monitoring_enabled: Keyword.get(opts, :monitoring_enabled, true)
88
    }
89

90
    Logger.info("MemoryManager started with monitoring enabled: #{state.monitoring_enabled}")
2✔
91
    {:ok, state}
92
  end
93

94
  # Public API
95

96
  @doc """
97
  Monitors current memory usage of the AST Repository.
98
  """
99
  @spec monitor_memory_usage() :: {:ok, memory_stats()} | {:error, term()}
100
  def monitor_memory_usage() do
101
    GenServer.call(__MODULE__, :monitor_memory_usage)
3✔
102
  end
103

104
  @doc """
105
  Cleans up unused AST data based on access patterns and age.
106
  """
107
  @spec cleanup_unused_data(keyword()) :: :ok | {:error, term()}
108
  def cleanup_unused_data(opts \\ []) do
109
    GenServer.call(__MODULE__, {:cleanup_unused_data, opts}, 30_000)
2✔
110
  end
111

112
  @doc """
113
  Compresses infrequently accessed analysis data.
114
  """
115
  @spec compress_old_analysis(keyword()) :: {:ok, compression_stats()} | {:error, term()}
116
  def compress_old_analysis(opts \\ []) do
117
    GenServer.call(__MODULE__, {:compress_old_analysis, opts}, 30_000)
2✔
118
  end
119

120
  @doc """
121
  Implements LRU (Least Recently Used) cache for query optimization.
122
  """
123
  @spec implement_lru_cache(atom(), keyword()) :: :ok | {:error, term()}
124
  def implement_lru_cache(cache_type, opts \\ []) do
125
    GenServer.call(__MODULE__, {:implement_lru_cache, cache_type, opts})
×
126
  end
127

128
  @doc """
129
  Handles memory pressure situations with appropriate response levels.
130
  """
131
  @spec memory_pressure_handler(atom()) :: :ok | {:error, term()}
132
  def memory_pressure_handler(pressure_level) do
133
    GenServer.call(__MODULE__, {:memory_pressure_handler, pressure_level}, 60_000)
5✔
134
  end
135

136
  @doc """
137
  Gets comprehensive memory and performance statistics.
138
  """
139
  @spec get_stats() :: {:ok, map()}
140
  def get_stats() do
141
    GenServer.call(__MODULE__, :get_stats)
5✔
142
  end
143

144
  @doc """
145
  Enables or disables memory monitoring.
146
  """
147
  @spec set_monitoring(boolean()) :: :ok
148
  def set_monitoring(enabled) do
149
    GenServer.call(__MODULE__, {:set_monitoring, enabled})
×
150
  end
151

152
  @doc """
153
  Forces garbage collection and memory optimization.
154
  """
155
  @spec force_gc() :: :ok
156
  def force_gc() do
157
    GenServer.call(__MODULE__, :force_gc)
×
158
  end
159

160
  # Cache API (delegated to CacheManager)
161

162
  @doc """
163
  Gets a value from the specified cache.
164
  """
165
  @spec cache_get(atom(), term()) :: {:ok, term()} | :miss
166
  def cache_get(cache_type, key) do
167
    CacheManager.get(cache_type, key)
56✔
168
  end
169

170
  @doc """
171
  Puts a value in the specified cache.
172
  """
173
  @spec cache_put(atom(), term(), term()) :: :ok
174
  def cache_put(cache_type, key, value) do
175
    CacheManager.put(cache_type, key, value)
59✔
176
  end
177

178
  @doc """
179
  Clears the specified cache.
180
  """
181
  @spec cache_clear(atom()) :: :ok
182
  def cache_clear(cache_type) do
183
    CacheManager.clear(cache_type)
1✔
184
  end
185

186
  # GenServer Callbacks
187

188
  def handle_call(:monitor_memory_usage, _from, state) do
189
    case Monitor.collect_memory_stats() do
2✔
190
      {:ok, memory_stats} ->
191
        new_state = %{state | memory_stats: memory_stats}
1✔
192
        {:reply, {:ok, memory_stats}, new_state}
1✔
193
      error ->
194
        {:reply, error, state}
1✔
195
    end
196
  end
197

198
  def handle_call({:cleanup_unused_data, opts}, _from, state) do
199
    start_time = System.monotonic_time(:millisecond)
2✔
200

201
    case Cleaner.perform_cleanup(opts) do
2✔
202
      {:ok, cleanup_result} ->
203
        end_time = System.monotonic_time(:millisecond)
2✔
204
        duration = end_time - start_time
2✔
205

206
        new_cleanup_stats = Cleaner.update_stats(state.cleanup_stats, cleanup_result, duration)
2✔
207
        new_state = %{state |
2✔
208
          cleanup_stats: new_cleanup_stats,
209
          last_cleanup: end_time
210
        }
211

212
        # Return the cleanup result, not just :ok
213
        {:reply, {:ok, cleanup_result}, new_state}
2✔
214

215
      error ->
216
        {:reply, error, state}
×
217
    end
218
  end
219

220
  def handle_call({:compress_old_analysis, opts}, _from, state) do
221
    start_time = System.monotonic_time(:millisecond)
2✔
222

223
    case Compressor.perform_compression(opts) do
2✔
224
      {:ok, compression_result} ->
225
        end_time = System.monotonic_time(:millisecond)
2✔
226
        duration = end_time - start_time
2✔
227

228
        new_compression_stats = Compressor.update_stats(state.compression_stats, compression_result, duration)
2✔
229
        new_state = %{state |
2✔
230
          compression_stats: new_compression_stats,
231
          last_compression: end_time
232
        }
233

234
        {:reply, {:ok, compression_result}, new_state}
2✔
235

236
      error ->
237
        {:reply, error, state}
×
238
    end
239
  end
240

241
  def handle_call({:implement_lru_cache, cache_type, opts}, _from, state) do
242
    case CacheManager.configure_cache(cache_type, opts) do
×
243
      :ok ->
244
        {:reply, :ok, state}
×
245
      error ->
246
        {:reply, error, state}
×
247
    end
248
  end
249

250
  def handle_call({:memory_pressure_handler, pressure_level}, _from, state) do
251
    case PressureHandler.handle_pressure(pressure_level) do
4✔
252
      :ok ->
253
        new_state = %{state | pressure_level: pressure_level}
3✔
254
        {:reply, :ok, new_state}
3✔
255
      error ->
256
        {:reply, error, state}
×
257
    end
258
  end
259

260
  def handle_call(:get_stats, _from, state) do
261
    cache_stats = CacheManager.get_stats()
5✔
262

263
    stats = %{
5✔
264
      memory: state.memory_stats,
5✔
265
      cache: cache_stats,
266
      cleanup: state.cleanup_stats,
5✔
267
      compression: state.compression_stats,
5✔
268
      pressure_level: state.pressure_level,
5✔
269
      monitoring_enabled: state.monitoring_enabled
5✔
270
    }
271
    {:reply, {:ok, stats}, state}
5✔
272
  end
273

274
  def handle_call({:set_monitoring, enabled}, _from, state) do
275
    new_state = %{state | monitoring_enabled: enabled}
×
276
    {:reply, :ok, new_state}
×
277
  end
278

279
  def handle_call(:force_gc, _from, state) do
280
    # Force garbage collection
281
    :erlang.garbage_collect()
×
282

283
    # Force GC on all processes
284
    for pid <- Process.list() do
×
285
      if Process.alive?(pid) do
×
286
        :erlang.garbage_collect(pid)
×
287
      end
288
    end
289

290
    {:reply, :ok, state}
×
291
  end
292

293
  def handle_info(:memory_check, state) do
294
    if state.monitoring_enabled do
×
295
      case Monitor.collect_memory_stats() do
×
296
        {:ok, memory_stats} ->
297
          # Check for memory pressure
298
          pressure_level = PressureHandler.determine_pressure_level(memory_stats.memory_usage_percent)
×
299

300
          if pressure_level != :normal and pressure_level != state.pressure_level do
×
301
            Logger.warning("Memory pressure detected: #{pressure_level} (#{memory_stats.memory_usage_percent}%)")
×
302
            PressureHandler.handle_pressure(pressure_level)
×
303
          end
304

305
          new_state = %{state |
×
306
            memory_stats: memory_stats,
307
            pressure_level: pressure_level
308
          }
309

310
          schedule_memory_check()
×
311
          {:noreply, new_state}
312

313
        {:error, reason} ->
314
          Logger.error("Memory monitoring failed: #{inspect(reason)}")
×
315
          schedule_memory_check()
×
316
          {:noreply, state}
317
      end
318
    else
319
      schedule_memory_check()
×
320
      {:noreply, state}
321
    end
322
  end
323

324
  def handle_info(:cleanup, state) do
325
    # Perform automatic cleanup
326
    Cleaner.perform_cleanup([max_age: 3600])
×
327

328
    schedule_cleanup()
×
329
    {:noreply, state}
330
  end
331

332
  def handle_info(:compression, state) do
333
    # Perform automatic compression
334
    Compressor.perform_compression([access_threshold: 5, age_threshold: 1800])
×
335

336
    schedule_compression()
×
337
    {:noreply, state}
338
  end
339

340
  # Private Implementation
341

342
  defp schedule_memory_check() do
343
    Process.send_after(self(), :memory_check, @memory_check_interval)
2✔
344
  end
345

346
  defp schedule_cleanup() do
347
    Process.send_after(self(), :cleanup, @cleanup_interval)
2✔
348
  end
349

350
  defp schedule_compression() do
351
    Process.send_after(self(), :compression, @compression_interval)
2✔
352
  end
353
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc