From 52cb73bea7c1f1a21212d0058439adb4ae47f7e7 Mon Sep 17 00:00:00 2001 From: Kevin Pouget <kevin.pouget@imag.fr> Date: Tue, 24 Jan 2017 14:43:24 +0100 Subject: [PATCH] add pagemap in the git tree --- model/numa/__init__.py | 2 +- model/numa/pagemap | 479 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 480 insertions(+), 1 deletion(-) create mode 100755 model/numa/pagemap diff --git a/model/numa/__init__.py b/model/numa/__init__.py index 4cdcaa8..7b78f12 100644 --- a/model/numa/__init__.py +++ b/model/numa/__init__.py @@ -16,7 +16,7 @@ except ImportError: numa_loaded = None -PAGEMAP_PATH = "/home/videau/" +PAGEMAP_PATH = "./" PAGEMAP = "pagemap" pagemap_bin = None diff --git a/model/numa/pagemap b/model/numa/pagemap new file mode 100755 index 0000000..fe1696c --- /dev/null +++ b/model/numa/pagemap @@ -0,0 +1,479 @@ +#!/usr/bin/ruby + +=begin +Copyright (c) 2013, Brice Videau <brice.videau@imag.fr> +Copyright (c) 2013, Vincent Danjean <Vincent.Danjean@ens-lyon.org> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +=end + +PAGEMAP_VERSION="1.1" + +require 'optparse' + +class MemoryArchitecture + NODE_PATH = "/sys/devices/system/node/" + MEMORY_PATH = "/sys/devices/system/memory/" + def initialize + @nodes = {} + block_size_bytes = File::read(MEMORY_PATH+"block_size_bytes") + block_size_bytes = block_size_bytes.to_i(16) + nodes = Dir.entries(NODE_PATH) + nodes = nodes.select { |entry| entry.match(/node\d+/) } + @memories = Hash::new { |hash,key| hash[key] = [] } + nodes.each { |node| + memories = Dir.entries(NODE_PATH+node) + memories = memories.select { |entry| entry.match(/memory\d+/) } + ranges = [] + number = node.scan(/(\d+)/).first[0] + number = number.to_i + memories.each { |memory| + phys_index = File::read(MEMORY_PATH+memory+"/phys_index") + phys_index = phys_index.to_i(16) + end_phys_index = File::read(MEMORY_PATH+memory+"/end_phys_index") + end_phys_index = end_phys_index.to_i(16) + start_address = phys_index*block_size_bytes + end_address = (end_phys_index-phys_index+1)*block_size_bytes+start_address + ranges.push(start_address...end_address) + @memories[start_address...end_address].push(number) + } + @nodes[number] = ranges + } + @memories = @memories.to_a + @memories.sort! { |a,b| a[0].end <= b[0].begin ? -1 : b[0].end <= a[0].begin ? +1 : raise( "Overlapping memory ranges!" ) } + end + + def binary_search(address, from, to) + low = from + high = to + while (low <= high) do + split = (low+high)/2 + if address < @memories[split][0].begin then + high = split - 1 + elsif address > @memories[split][0].end then + low = split + 1 + else + return split + end + end + raise "Could not find address #{address}!" + end + + def find_node(address) + index = binary_search(address, 0, @memories.length - 1) + return [] if not @memories[index][0].include?(address) + result = @memories[index][1] + return result + end + +end + +class Flags + attr_accessor :value + HACK_FLAGS = [ ["RESERVED",32,"r"], ["MLOCKED",33,"m"], ["MAPPEDTODISK",34,"d"], ["PRIVATE",35,"P"], ["PRIVATE_2",36,"p"], ["OWNER_PRIVATE",37,"O"], ["ARCH",38,"h"], ["UNCACHED",39,"c"]] + FLAGS = [["LOCKED",0,"L"], ["ERROR",1,"E"], ["REFERENCED",2,"R"], ["UPTODATE",3,"U"], ["DIRTY",4,"D"], ["LRU",5,"l"], ["ACTIVE",6,"A"], ["SLAB",7,"S"], ["WRITEBACK",8,"W"], ["RECLAIM",9,"I"], ["BUDDY",10,"B"], ["MMAP",11,"M"], ["ANON",12,"a"], ["SWAPCACHE",13,"s"], ["SWAPBACKED",14,"b"], ["COMPOUND_HEAD",15,"H"], ["COMPOUND_TAIL",16,"T"], ["HUGE",17,"G"], ["UNEVICTABLE",18,"u"], ["HWPOISON",19,"X"], ["NOPAGE",20,"n"], ["KSM",21,"x"], ["THP",22,"t"]] + def initialize(value) + @value = value + end + def Flags.doc + s = "" + print_flag = Proc.new { |flag, val, sym| s += "#{sym}:#{flag} (#{val})\n" } + FLAGS.each( &print_flag ) + HACK_FLAGS.each( &print_flag ) if $options[:hack] + return s + end + def to_s + flags = [] + join_char = "" + join_char = " " if not $options[:symbols] + if $options[:symbols] then + process_flag = Proc.new { |flag, val, sym| + if ( (@value >> val) & 1) == 1 then + flags.push(sym) + else + flags.push("_") + end + } + else + process_flag = Proc.new { |flag, val| flags.push(flag) if ( (@value >> val) & 1) == 1 } + end + FLAGS.each(&process_flag) + HACK_FLAGS.each(&process_flag) if $options[:hack] + return flags.join(join_char) + end +end + +class Page + attr_accessor :pfn, :swap_type, :swap_offset, :page_shift, :reserved, :swapped, :present, :count, :flags, :nodes + def Page.decode(code) + present = ((code >> 63) & 1) == 1 + swapped = ((code >> 62) & 1) == 1 + reserved = ((code >> 61) & 1) == 1 + pfn = code & 0x7fffffffffffff + swap_type = code & 0x1f + swap_offset = (code & 0x7fffffffffffe0) >> 5 + page_shift = $page_shift + return Page::new(pfn, swap_type, swap_offset, page_shift, reserved, swapped, present) + end + def initialize(pfn, swap_type, swap_offset, page_shift, reserved, swapped, present) + @pfn, @swap_type, @swap_offset, @page_shift, @reserved, @swapped, @present = pfn, swap_type, swap_offset, page_shift, reserved, swapped, present + @count = nil + if $kpagecount and @present then + $kpagecount.seek(@pfn*8) + str = $kpagecount.read(8) + if str then + @count = str.unpack('Q').first + end + end + @flags = nil + if $kpageflags and @present then + $kpageflags.seek(@pfn*8) + str = $kpageflags.read(8) + if str then + @flags = Flags::new(str.unpack('Q').first) + end + end + if $memory_architecture and @present then + @nodes = $memory_architecture.find_node(@pfn << @page_shift) + end + end + def to_s + s = "" + if @present then + s += (@pfn << @page_shift).to_s(16) + s += " " + @count.to_s if @count + s += " " + @flags.to_s if @flags + if @nodes then + @nodes.each { |node| + s += " N#{node}" + } + end + return s + end + return "swapped" if @swapped + return "absent" + end + def absent + return ((!@present) && (!@swapped)) + end +end + +class PageRange < Range + def initialize(address) + a_start, a_end = address.split("-") + a_start = a_start.to_i(16) + a_end = a_end.to_i(16) + super(a_start, a_end, true) + end + def to_s + return self.begin.to_s(16)+"-"+self.end.to_s(16) + end + def size + return self.end - self.begin + end + def number + return (self.end - self.begin)/$page_size + end + def each_page(&proc) + self.step($page_size, &proc) + end +end + +class Map + attr_accessor :address + attr_accessor :perms + attr_accessor :offset + attr_accessor :device + attr_accessor :inode + attr_accessor :pathname + attr_accessor :pages + attr_accessor :numa_policy + attr_accessor :numa_informations + + def Map.convert(line,match="") + address, perms, offset, device, inode, pathname = line.scan(/([0-9a-fA-F]+-[0-9a-fA-F]+)\s+([rwxsp-]+)\s+([0-9a-fA-F]+)\s+(\S+)\s+([0-9a-fA-F]+)\s+(\S*)/).first + address = PageRange::new(address) + offset = offset.to_i(16) + inode = inode.to_i + return Map::new(address, perms, offset, device, inode, pathname) if pathname.match(match) + return nil + end + def initialize(address, perms, offset, device, inode, pathname) + @address, @perms, @offset, @device, @inode, @pathname = address, perms, offset, device, inode, pathname + @pages = {} + @address.each_page{ |page| + $pagemap.seek((page/$page_size)*8) + str = $pagemap.read(8) + if str then + @pages[page] = Page::decode(str.unpack('Q').first) + end + } + end + def to_s + return @address.to_s + " " + @perms + " " + @offset.to_s(16) + " " + @device + " " + @inode.to_s + " " + @pathname + end + def add_numa(numa_map) + @numa_policy = numa_map.policy + @numa_informations = numa_map.informations + end +end +class NumaInformations < Hash + attr_accessor :nodes + def initialize(informations) + infos = informations.scan(/\S+/) + @nodes = {} + infos.each { |info| + key,value = info.split("=") + if num = key.scan(/N(\d+)/).first then + @nodes[num.pop] = value + else + self[key] = value + end + } + end + def to_s + s = [] + self.each { |key, value| + str = "#{key}" + str += "=#{value}" if value + s.push( str ) + } + @nodes.each { |key, value| + str = "N#{key}=#{value}" + s.push( str ) + } + return s.join(" ") + end + def stack? + self.each_key { |key| + return true if key.match(/stack/) + } + return false + end +end +class NumaMap + attr_accessor :address + attr_accessor :policy + attr_accessor :informations + def NumaMap.convert(line) + address, policy, informations = line.scan(/([0-9a-fA-F]+)\s+(\w+)\s*(.*)/).first + address = address.to_i(16) + informations = NumaInformations::new(informations) + address += $page_size if informations.stack? #remove stack guard page + return NumaMap::new(address, policy, informations) + end + def initialize(address, policy, informations) + @address, @policy, @informations = address, policy, informations + end +end + +def usage(msg=nil) + puts msg if msg + puts $parser.to_s + exit(0) +end + +$options = {:match => "", :base => 16} + +$parser = OptionParser::new do |opts| + opts.banner = "Usage: pagemap [options] [pid [address[-address][,address[-address]...]]]" + opts.on("-b", "--base [BASE]", Integer, "Address base") do |base| + $options[:base] = base + end + opts.on("-y", "--[no-]yaml","YAML output") do |yaml| + $options[:yaml] = yaml + end + opts.on("-m", "--match [MATCH]", "Pathname match") do |match| + $options[:match] = match + end + opts.on("-a", "--[no-]all", "List absent pages") do |v| + $options[:all] = v + end + opts.on("-k", "--[no-]hack", "Use kernel hack flags (unreliable)") do |v| + $options[:hack] = v + end + opts.on("-s", "--[no-]symbols", "Use flags symbols instead of name") do |v| + $options[:symbols] = v + end + opts.on("-n", "--[no-]numa", "Add numa information") do |v| + $options[:numa] = v + end + opts.on("-r", "--ranges [address[-address][,address[-address]...]]", "Specify address ranges") do |range| + $options[:ranges] = range + end + opts.on("-d", "--[no-]debug", "Print debug messages") do |v| + $options[:debug] = v + end + opts.on("-h", "--help", "Show this message") do + puts <<EOF +pagemap is a simple command line tool to analyze and print the physical memory +layout of a Linux process. It is used to debug and interpret performances of +standard or HPC applications. + +EOF + puts opts + exit + end + opts.on("--version", "Display version") do + puts "pagemap #{PAGEMAP_VERSION}" + exit + end + opts.on("--help-symbols", "Show flags symbols") do + puts opts + puts "Flags symbols:" + Flags.doc.each_line { |line| puts "\t#{line}" } + exit + end + opts.parse! +end +addresses = [] + +if ARGV.length > 2 then + usage("Invalid number of arguments (#{ARGV.length})!") +end + +if ARGV.length == 0 then + pid = Process.ppid +else + pid = ARGV[0] + pid = pid.to_i + if pid == 0 then + usage("Invalid pid!") + end +end + +addresses = ARGV[1].split(",") if ARGV.length == 2 +addresses += $options[:ranges].split(",") if $options[:ranges] + + +$page_size = `getconf PAGESIZE`.to_i +ps = $page_size +$page_shift = 0 +while ps & 1 == 0 do + $page_shift += 1 + ps >>= 1 +end +$pagemap = File::open("/proc/#{pid}/pagemap") +numa_maps_lines = nil +$memory_architecture = nil +if $options[:numa] then + begin + numa_maps_lines = File::open("/proc/#{pid}/numa_maps") + rescue Exception => e + $stderr.puts e if $options[:debug] + numa_maps_lines = nil + end + begin + $memory_architecture = MemoryArchitecture::new + rescue Exception => e + $stderr.puts e if $options[:debug] + $memory_architecture = nil + end +end + +begin + $kpagecount = File::open("/proc/kpagecount") +rescue Exception => e + $stderr.puts e if $options[:debug] + $kpagecount = nil +end + +begin + $kpageflags = File::open("/proc/kpageflags") +rescue Exception => e + $stderr.puts e if $options[:debug] + $kpageflags = nil +end + + +if addresses.length != 0 then + pages = {} + addresses_unranged = [] + addresses.each { |range| + addrs = range.split("-") + addrs.collect! { |addr| addr.to_i($options[:base]) } + find_and_decode = lambda { |address| + page = nil + $pagemap.seek((address/$page_size)*8) + str = $pagemap.read(8) + page = Page::decode(str.unpack('Q').first) if str + pages[address] = page + addresses_unranged.push(address) + } + if addrs.length == 2 then + if addrs[0] % $page_size != 0 then + find_and_decode.call(addrs[0]) if addrs[0] % $page_size != 0 + addrs[0] += $page_size - (addrs[0] % $page_size) + end + (addrs[0] - ( addrs[0] % $page_size )).step(addrs[1],$page_size,&find_and_decode) + else + find_and_decode.call(addrs[0]) + end + } + if $options[:yaml] then + require 'yaml' + puts YAML::dump(pages) + else + addresses_unranged.each { |address| + p = pages[address] + if p && (!p.absent || $options[:all]) then + puts address.to_s(16) + " " + p.to_s + end + } + end +else + numa_maps = {} + if numa_maps_lines then + numa_maps_lines.each { |line| + numa_map = NumaMap::convert(line) + numa_maps[numa_map.address] = numa_map if numa_map + } + end + maps_lines = File::open("/proc/#{pid}/maps") + maps = [] + maps_lines.each { |line| + map = Map::convert(line,$options[:match]) + if map then + numa_maps.each { |key, value| + map.add_numa(value) if map.address.include?(key) + } + maps.push(map) + end + } + if $options[:yaml] then + require 'yaml' + puts YAML::dump(maps) + else + maps.each { |map| + puts '## mapping: ' + map.to_s + puts '## mapping size: ' + (map.address.size/1024).to_s + "kB" + + ' / number of pages: ' + map.address.number.to_s + puts '## numa policy: ' + map.numa_policy.to_s + ' / numa informations: ' + map.numa_informations.to_s if $options[:numa] + map.address.each_page {|x| + p = map.pages[x] + if p && (!p.absent || $options[:all]) then + puts x.to_s(16) + " " + p.to_s + end + } + } + end +end -- GitLab