summaryrefslogtreecommitdiff
path: root/test/lib/memory_status.rb
blob: b275db05e6db63b874a8e6f58a8ba2d6a8e56a29 (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
module Memory
  keys = []
  vals = []

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

    read_status {|k, v| keys << k; vals << v}

  when /mswin|mingw/ =~ RUBY_PLATFORM
    require 'fiddle/import'
    require 'fiddle/types'

    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

    keys << :peak << :size
    def self.read_status
      if info = Win32.memory_info
        yield :peak, info.PeakPagefileUsage
        yield :size, info.PagefileUsage
      end
    end
  else
    PAT = /^\s*(\d+)\s+(\d+)$/
    require_relative 'find_executable'
    if PSCMD = EnvUtil.find_executable("ps", "-ovsz=", "-orss=", "-p", $$.to_s) {|out| PAT =~ out}
      PSCMD.pop
    end
    raise MiniTest::Skip, "ps command not found" unless 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
  end

  Status = Struct.new(*keys)

  class Status
    def _update
      Memory.read_status do |key, val|
        self[key] = val
      end
    end
  end

  class Status
    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
        NO_MEMORY_LEAK_ENVS = envs
      end
    end
  end #case RUBY_PLATFORM

end