File: cli.rb

package info (click to toggle)
ruby-memory-profiler 1.1.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 284 kB
  • sloc: ruby: 1,128; makefile: 4
file content (146 lines) | stat: -rw-r--r-- 4,259 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# frozen_string_literal: true

require "optparse"

module MemoryProfiler
  class CLI
    BIN_NAME     = "ruby-memory-profiler"
    VERSION_INFO = "#{BIN_NAME}  #{MemoryProfiler::VERSION}"

    STATUS_SUCCESS = 0
    STATUS_ERROR   = 1

    DEFAULTS = {
      ignore_files: "memory_profiler/lib"
    }.freeze

    def run(argv)
      options = {}
      parser = option_parser(options)
      parser.parse!(argv)

      options = DEFAULTS.merge(options)

      # Make sure the user specified at least one file
      unless (script = argv.shift)
        puts parser
        puts ""
        puts "#{VERSION_INFO}  |  ERROR: Must specify a script to run"
        return STATUS_ERROR
      end

      if script == "run"
        # We are profiling a command.
        profile_command(options, argv)
      else
        # We are profiling a ruby file.
        begin
          MemoryProfiler.start(options)
          load(script)
        ensure
          report = MemoryProfiler.stop
          report.pretty_print(**options)
        end
        STATUS_SUCCESS
      end
    rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument => e
      puts parser
      puts e.message
      STATUS_ERROR
    end

    private

    def option_parser(options)
      OptionParser.new do |opts|
        opts.banner = <<~BANNER

              #{VERSION_INFO}
              A Memory Profiler for Ruby

          Usage:
              #{BIN_NAME} [options] run [--] command [command-options]
        BANNER

        opts.separator ""
        opts.separator "Options:"

        # Reporter options
        opts.on("-m", "--max=NUM", Integer, "Max number of entries to output. (Defaults to 50)") do |arg|
          options[:top] = arg
        end

        opts.on("--classes=CLASSES", Array, "A class or list of classes you explicitly want to trace.") do |arg|
          options[:trace] = arg.map { |klass| Object.const_get(klass) }
        end

        opts.on("--ignore-files=REGEXP", "A regular expression used to exclude certain files from tracing.") do |arg|
          options[:ignore_files] = "#{arg}|memory_profiler/lib"
        end

        opts.on("--allow-files=FILES", Array, "A string or list of strings to selectively include in tracing.") do |arg|
          options[:allow_files] = arg
        end

        opts.separator ""

        # Results options
        opts.on("-o", "--out=FILE", "Write output to a file instead of STDOUT.") do |arg|
          options[:to_file] = arg
        end

        opts.on("--[no-]color", "Force color output on or off. (Enabled by default)") do |arg|
          options[:color_output] = arg
        end

        opts.on("--retained-strings=NUM", Integer, "How many retained strings to print.") do |arg|
          options[:retained_strings] = arg
        end

        opts.on("--allocated-strings=NUM", Integer, "How many allocated strings to print.") do |arg|
          options[:allocated_strings] = arg
        end

        opts.on("--[no-]detailed", "Print detailed information. (Enabled by default)") do |arg|
          options[:detailed_report] = arg
        end

        opts.on("--scale-bytes", "Calculates unit prefixes for the numbers of bytes.") do
          options[:scale_bytes] = true
        end

        opts.on("--normalize-paths", "Print location paths relative to gem's source directory.") do
          options[:normalize_paths] = true
        end

        opts.on("--pretty", "Easily enable options 'scale-bytes' and 'normalize-paths'") do
          options[:scale_bytes] = options[:normalize_paths] = true
        end

        opts.separator ""

        opts.on_tail("-h", "--help", "Show this help message.") do
          puts opts
          exit
        end

        opts.on_tail("-v", "--version", "Show program version.") do
          puts VERSION_INFO
          exit
        end
      end
    end

    def profile_command(options, argv)
      env = {}
      env["MEMORY_PROFILER_OPTIONS"] = serialize_hash(options) if options.any?
      gem_path = File.expand_path('../', __dir__)
      env["RUBYOPT"] = "-I #{gem_path} -r memory_profiler/autorun #{ENV['RUBYOPT']}"
      exec(env, *argv)
    end

    def serialize_hash(hash)
      [Marshal.dump(hash)].pack("m0")
    end
  end
end