1## This Source Code Form is subject to the terms of the Mozilla Public
2## License, v. 2.0. If a copy of the MPL was not distributed with this
3## file, You can obtain one at https://mozilla.org/MPL/2.0/.
4##
5## Copyright (c) 2007-2021 VMware, Inc. or its affiliates.  All rights reserved.
6
7defmodule RabbitMQ.CLI.Diagnostics.Commands.MemoryBreakdownCommand do
8  alias RabbitMQ.CLI.InformationUnit, as: IU
9  import RabbitMQ.CLI.Core.Memory
10
11  @behaviour RabbitMQ.CLI.CommandBehaviour
12
13  def switches(), do: [unit: :string, timeout: :integer]
14  def aliases(), do: [t: :timeout]
15
16  def merge_defaults(args, opts) do
17    {args, Map.merge(%{unit: "gb"}, opts)}
18  end
19
20  def validate(args, _) when length(args) > 0 do
21    {:validation_failure, :too_many_args}
22  end
23
24  def validate(_, %{unit: unit}) do
25    case IU.known_unit?(unit) do
26      true ->
27        :ok
28
29      false ->
30        {:validation_failure, "unit '#{unit}' is not supported. Please use one of: bytes, mb, gb"}
31    end
32  end
33
34  use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
35
36  def run([], %{node: node_name, timeout: timeout}) do
37    :rabbit_misc.rpc_call(node_name, :rabbit_vm, :memory, [], timeout)
38  end
39
40  def output(result, %{formatter: "json"} = _opts) do
41    {:ok, compute_relative_values(result)}
42  end
43
44  def output(result, %{formatter: "csv"} = _opts) do
45    flattened =
46      compute_relative_values(result)
47      |> Enum.flat_map(fn {k, %{bytes: b, percentage: p}} ->
48        [{"#{k}.bytes", b}, {"#{k}.percentage", p}]
49      end)
50      |> Enum.sort_by(fn {key, _val} -> key end, &>=/2)
51
52    headers = Enum.map(flattened, fn {k, _v} -> k end)
53    values = Enum.map(flattened, fn {_k, v} -> v end)
54
55    {:stream, [headers, values]}
56  end
57
58  def output(result, _opts) do
59    {:ok, compute_relative_values(result)}
60  end
61
62  def help_section(), do: :observability_and_health_checks
63
64  def description(), do: "Provides a memory usage breakdown on the target node."
65
66  def usage, do: "memory_breakdown [--unit <unit>]"
67
68  def usage_additional() do
69    [
70      ["--unit <bytes | mb | gb>", "byte multiple (bytes, megabytes, gigabytes) to use"],
71      ["--formatter <json | csv | erlang>", "alternative formatter to use, JSON, CSV or Erlang terms"]
72    ]
73  end
74
75  def banner([], %{node: node_name}) do
76    "Reporting memory breakdown on node #{node_name}..."
77  end
78
79  defmodule Formatter do
80    alias RabbitMQ.CLI.Formatters.FormatterHelpers
81    alias RabbitMQ.CLI.InformationUnit, as: IU
82
83    @behaviour RabbitMQ.CLI.FormatterBehaviour
84
85    def format_output(output, %{unit: unit}) do
86      Enum.reduce(output, "", fn {key, %{bytes: bytes, percentage: percentage}}, acc ->
87        u = String.downcase(unit)
88        acc <> "#{key}: #{IU.convert(bytes, u)} #{u} (#{percentage}%)\n"
89      end)
90    end
91
92    def format_stream(stream, options) do
93      Stream.map(
94        stream,
95        FormatterHelpers.without_errors_1(fn el ->
96          format_output(el, options)
97        end)
98      )
99    end
100  end
101
102  def formatter(), do: Formatter
103end
104