summaryrefslogtreecommitdiff
path: root/tool/lib/memory_status.rb
blob: 429e5f6a1d3fa9fe30d377325c2b01471b676e33 (plain)
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# frozen_string_literal: true
begin
  require '-test-/memory_status.so'
rescue LoadError
end

module Memory
  keys = []

  case
  when File.exist?(procfile = "/proc/self/status") && (pat = /^Vm(\w+):\s+(\d+)/) =~ (data = File.binread(procfile))
    PROC_FILE = procfile
    VM_PAT = pat
    def self.read_status
      File.foreach(PROC_FILE, encoding: Encoding::ASCII_8BIT) do |l|
        yield($1.downcase.intern, $2.to_i * 1024) if VM_PAT =~ l
      end
    end

    data.scan(pat) {|k, v| keys << k.downcase.intern}

  when /mswin|mingw/ =~ RUBY_PLATFORM
    keys.push(:size, :rss, :peak)

    begin
      require 'fiddle/import'
      require 'fiddle/types'
    rescue LoadError
      # Fallback to PowerShell command to get memory information for current process
      def self.read_status
        cmd = [
          "powershell.exe", "-NoProfile", "-Command",
          "Get-Process -Id #{$$} | " \
          "% { Write-Output $_.PagedMemorySize64 $_.WorkingSet64 $_.PeakWorkingSet64 }"
        ]

        IO.popen(cmd, "r", err: [:child, :out]) do |out|
          if /^(\d+)\n(\d+)\n(\d+)$/ =~ out.read
            yield :size, $1.to_i
            yield :rss, $2.to_i
            yield :peak, $3.to_i
          end
        end
      end
    else
      module Win32
        extend Fiddle::Importer
        dlload "kernel32.dll", "psapi.dll"
        include Fiddle::Win32Types
        typealias "SIZE_T", "size_t"

        PROCESS_MEMORY_COUNTERS = struct [
          "DWORD  cb",
          "DWORD  PageFaultCount",
          "SIZE_T PeakWorkingSetSize",
          "SIZE_T WorkingSetSize",
          "SIZE_T QuotaPeakPagedPoolUsage",
          "SIZE_T QuotaPagedPoolUsage",
          "SIZE_T QuotaPeakNonPagedPoolUsage",
          "SIZE_T QuotaNonPagedPoolUsage",
          "SIZE_T PagefileUsage",
          "SIZE_T PeakPagefileUsage",
        ]

        typealias "PPROCESS_MEMORY_COUNTERS", "PROCESS_MEMORY_COUNTERS*"

        extern "HANDLE GetCurrentProcess()", :stdcall
        extern "BOOL GetProcessMemoryInfo(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD)", :stdcall

        module_function
        def memory_info
          size = PROCESS_MEMORY_COUNTERS.size
          data = PROCESS_MEMORY_COUNTERS.malloc
          data.cb = size
          data if GetProcessMemoryInfo(GetCurrentProcess(), data, size)
        end
      end

      def self.read_status
        if info = Win32.memory_info
          yield :size, info.PagefileUsage
          yield :rss, info.WorkingSetSize
          yield :peak, info.PeakWorkingSetSize
        end
      end
    end
  when (require_relative 'find_executable'
        pat = /^\s*(\d+)\s+(\d+)$/
        pscmd = EnvUtil.find_executable("ps", "-ovsz=", "-orss=", "-p", $$.to_s) {|out| pat =~ out})
    pscmd.pop
    PAT = pat
    PSCMD = pscmd

    keys << :size << :rss
    def self.read_status
      if PAT =~ IO.popen(PSCMD + [$$.to_s], "r", err: [:child, :out], &:read)
        yield :size, $1.to_i*1024
        yield :rss, $2.to_i*1024
      end
    end
  else
    def self.read_status
      raise NotImplementedError, "unsupported platform"
    end
  end

  if !keys.empty?
    Status = Struct.new(*keys)
  end
end unless defined?(Memory::Status)

if defined?(Memory::Status)
  class Memory::Status
    def _update
      Memory.read_status do |key, val|
        self[key] = val
      end
      self
    end unless method_defined?(:_update)

    Header = members.map {|k| k.to_s.upcase.rjust(6)}.join('')
    Format = "%6d"

    def initialize
      _update
    end

    def to_s
      status = each_pair.map {|n,v|
        "#{n}:#{v}"
      }
      "{#{status.join(",")}}"
    end

    def self.parse(str)
      status = allocate
      str.scan(/(?:\A\{|\G,)(#{members.join('|')}):(\d+)(?=,|\}\z)/) do
        status[$1] = $2.to_i
      end
      status
    end
  end

  # On some platforms (e.g. Solaris), libc malloc does not return
  # freed memory to OS because of efficiency, and linking with extra
  # malloc library is needed to detect memory leaks.
  #
  case RUBY_PLATFORM
  when /solaris2\.(?:9|[1-9][0-9])/i # Solaris 9, 10, 11,...
    bits = [nil].pack('p').size == 8 ? 64 : 32
    if ENV['LD_PRELOAD'].to_s.empty? &&
        ENV["LD_PRELOAD_#{bits}"].to_s.empty? &&
        (ENV['UMEM_OPTIONS'].to_s.empty? ||
         ENV['UMEM_OPTIONS'] == 'backend=mmap') then
      envs = {
        'LD_PRELOAD' => 'libumem.so',
        'UMEM_OPTIONS' => 'backend=mmap'
      }
      args = [
              envs,
              "--disable=gems",
              "-v", "-",
             ]
      _, err, status = EnvUtil.invoke_ruby(args, "exit(0)", true, true)
      if status.exitstatus == 0 && err.to_s.empty? then
        Memory::NO_MEMORY_LEAK_ENVS = envs
      end
    end
  end #case RUBY_PLATFORM

end