Current section
Files
Jump to
Current section
Files
lib/npm/cache_stats.ex
defmodule NPM.CacheStats do @moduledoc """ Provides statistics about the npm package cache. """ @doc """ Computes cache hit/miss statistics for a lockfile. """ @spec hit_miss(map()) :: %{ hits: non_neg_integer(), misses: non_neg_integer(), total: non_neg_integer() } def hit_miss(lockfile) do results = Enum.map(lockfile, fn {name, entry} -> NPM.Cache.cached?(name, entry.version) end) hits = Enum.count(results, & &1) total = length(results) %{hits: hits, misses: total - hits, total: total} end @doc """ Computes the hit rate as a percentage. """ @spec hit_rate(map()) :: float() def hit_rate(lockfile) do stats = hit_miss(lockfile) if stats.total == 0, do: 100.0, else: Float.round(stats.hits / stats.total * 100, 1) end @doc """ Estimates cache size on disk. """ @spec disk_size() :: non_neg_integer() def disk_size do cache_dir = Path.join(NPM.Cache.dir(), "cache") if File.exists?(cache_dir) do walk_size(cache_dir) else 0 end end @doc """ Lists cached packages with versions. """ @spec list_cached() :: [{String.t(), [String.t()]}] def list_cached do cache_dir = Path.join(NPM.Cache.dir(), "cache") if File.exists?(cache_dir) do cache_dir |> File.ls!() |> Enum.flat_map(&list_package_versions(cache_dir, &1)) |> Enum.sort_by(&elem(&1, 0)) else [] end end @doc """ Formats cache statistics for display. """ @spec format(map()) :: String.t() def format(stats) do rate = if stats.total == 0, do: 100.0, else: Float.round(stats.hits / stats.total * 100, 1) "Cache: #{stats.hits}/#{stats.total} hits (#{rate}%), #{stats.misses} to fetch" end @doc """ Formats a byte size in human-readable form. """ @spec format_size(non_neg_integer()) :: String.t() defdelegate format_size(bytes), to: NPM.FormatUtil defp walk_size(path) do case File.stat(path) do {:ok, %{type: :regular, size: size}} -> size {:ok, %{type: :directory}} -> path |> File.ls!() |> Enum.reduce(0, fn entry, acc -> acc + walk_size(Path.join(path, entry)) end) _ -> 0 end end defp list_package_versions(cache_dir, name) do pkg_dir = Path.join(cache_dir, name) case File.ls(pkg_dir) do {:ok, versions} -> dirs = Enum.filter(versions, &File.dir?(Path.join(pkg_dir, &1))) if dirs == [], do: [], else: [{name, Enum.sort(dirs)}] _ -> [] end endend