From d8ec3ff5d49e2e6b45d0d27eb00be50437d8dea3 Mon Sep 17 00:00:00 2001
From: Florent Didier <florent.didier@inria.fr>
Date: Tue, 6 Feb 2018 18:11:02 +0100
Subject: [PATCH] [dev] hardware.rb: add this wiki generator

---
 generators/wiki/hardware.rb | 285 ++++++++++++++++++++++++++++++++++++
 1 file changed, 285 insertions(+)
 create mode 100644 generators/wiki/hardware.rb

diff --git a/generators/wiki/hardware.rb b/generators/wiki/hardware.rb
new file mode 100644
index 00000000000..d495c1bb11c
--- /dev/null
+++ b/generators/wiki/hardware.rb
@@ -0,0 +1,285 @@
+# coding: utf-8
+require 'pp'
+require_relative '../lib/input_loader'
+require_relative './wiki_generator'
+require_relative './mw_utils'
+require_relative './site_hardware.rb'
+
+class G5KHardwareGenerator < WikiGenerator
+
+  def initialize(page_name)
+    super(page_name)
+  end
+
+  def generate_content
+    @global_hash = load_yaml_file_hierarchy(File.expand_path('../../input/grid5000/', File.dirname(__FILE__)))
+    @site_uids = G5K::SITES
+    
+    @generated_content = "__NOEDITSECTION__\n"
+    @generated_content += "\n= Clusters =\n"
+    @generated_content += SiteHardwareGenerator.generate_all_clusters
+    @generated_content += generate_totals
+    @generated_content += MW.italic(MW.small("Generated from the Grid5000 APIs on " + Time.now.strftime("%Y-%m-%d")))
+    @generated_content += MW::LINE_FEED
+  end
+
+  def generate_totals
+    data = {
+      'proc_families' => {},
+      'proc_models' => {},
+      'core_models' => {},
+      'ram_size' => {},
+      'net_interconnects' => {},
+      'acc_families' => {},
+      'acc_models' => {},
+      'acc_cores' => {},
+      'node_models' => {}
+    }
+    
+    @global_hash['sites'].sort.each { |site_uid, site_hash|
+      site_hash['clusters'].sort.each { |cluster_uid, cluster_hash|
+        cluster_hash['nodes'].sort.each { |node_uid, node_hash|
+          next if node_hash['status'] == 'retired'
+          @node = node_uid
+          
+          # Processors
+          model = node_hash['processor']['model']
+          version = "#{model} #{node_hash['processor']['version']}"
+          microarchitecture = node_hash['processor']['microarchitecture']
+
+          cluster_procs = node_hash['architecture']['nb_procs']
+          cluster_cores = node_hash['architecture']['nb_cores']
+
+          key = [model]
+          init(data, 'proc_families', key)
+          data['proc_families'][key][site_uid] += cluster_procs
+
+          key = [{text: microarchitecture || ' ', sort: get_date(microarchitecture) + ', ' + microarchitecture.to_s}, {text: version, sort: get_date(microarchitecture) + ', ' + version.to_s}]
+          init(data, 'proc_models', key)
+          data['proc_models'][key][site_uid] += cluster_procs
+
+          init(data, 'core_models', key)
+          data['core_models'][key][site_uid] += cluster_cores
+          
+          # RAM size
+          ram_size = node_hash['main_memory']['ram_size']
+          key = [{ text: G5K.get_size(ram_size), sort: (ram_size / 2**30).to_s.rjust(6, '0') + ' GB' }]
+          init(data, 'ram_size', key)
+          data['ram_size'][key][site_uid] += 1
+
+          # HPC Networks
+          interfaces = node_hash['network_adapters']
+                       .select{ |k, v| v['enabled'] and (v['mounted'] or v['mountable']) and not v['management'] }
+                       .map{ |k, v| [{text: v['interface'] + ' ' + G5K.get_rate(v['rate']), sort: ((v['rate'])/10**6).to_s.rjust(6, '0') + ' Gbps, ' + v['interface']}] }
+                       .uniq
+
+          net_interconnects = interfaces.inject(Hash.new(0)){ |h, v| h[v] += 1; h }
+          net_interconnects.each { |k, v|
+            init(data, 'net_interconnects', k)
+            data['net_interconnects'][k][site_uid] += v
+          }
+
+          # Accelerators
+          g = node_hash['gpu']
+          m = node_hash['mic']        
+
+          gpu_families = {}
+          gpu_families[[g['gpu_vendor']]] = g['gpu_count'] if g and g['gpu']
+          mic_families = {}
+          mic_families[[m['mic_vendor']]] = m['mic_count'] if m and m['mic']
+          gpu_families.merge(mic_families).each { |k, v|
+            init(data, 'acc_families', k) 
+            data['acc_families'][k][site_uid] += v
+          }
+
+          gpu_details = {}
+          gpu_details[["#{g['gpu_vendor']} #{g['gpu_model']}"]] = [g['gpu_count'], g['gpu_cores']] if g and g['gpu']
+          mic_details = {}
+          mic_details[["#{m['mic_vendor']} #{m['mic_model']}"]] = [m['mic_count'], m['mic_cores']] if m and m['mic']
+          
+          gpu_details.merge(mic_details).each { |k, v|
+            init(data, 'acc_models', k)
+            data['acc_models'][k][site_uid] += v[0]
+
+            init(data, 'acc_cores', k)
+            data['acc_cores'][k][site_uid] += v[1]
+          }
+
+          # Nodes
+          key = [cluster_hash['model']]
+          init(data, 'node_models', key)
+          data['node_models'][key][site_uid] += 1
+        }
+      }
+    }
+
+    # Table construction
+    generated_content = "= Processors ="
+    generated_content += "\n== Processors counts per families ==\n"
+    sites = @site_uids.map{ |e| "[[#{e.capitalize}:Hardware|#{e.capitalize}]]" }
+    table_options = 'class="wikitable sortable" style="text-align: center;"'
+    table_columns = ['Processor family'] + sites + ['Processors total']
+    generated_content += MW.generate_table(table_options, table_columns, get_table_data(data, 'proc_families'))
+    generated_content += "\n== Processors counts per models ==\n"
+    table_columns = ['Microarchitecture', 'Processor model'] + sites + ['Processors total']
+    generated_content += MW.generate_table(table_options, table_columns, get_table_data(data, 'proc_models'))
+    generated_content += "\n== Cores counts per models ==\n"
+    table_columns =  ['Microarchitecture', 'Core model'] + sites + ['Cores total']
+    generated_content += MW.generate_table(table_options, table_columns, get_table_data(data, 'core_models'))
+
+    generated_content += "\n= RAM size per node =\n"
+    table_columns = ['RAM size'] + sites + ['Nodes total']
+    generated_content += MW.generate_table(table_options, table_columns, get_table_data(data, 'ram_size'))
+
+    generated_content += "\n= Network interconnects =\n"
+    table_columns = ['Interconnect'] + sites + ['Cards total']
+    generated_content += MW.generate_table(table_options, table_columns, get_table_data(data, 'net_interconnects'))
+
+    generated_content += "\n= Nodes with several Ethernet interfaces =\n"
+    generated_content +=  generate_interfaces
+    
+    generated_content += "\n= Accelerators (GPU, Xeon Phi) ="
+    generated_content += "\n== Accelerator families ==\n"
+    table_columns = ['Accelerator family'] + sites + ['Accelerators total']
+    generated_content += MW.generate_table(table_options, table_columns, get_table_data(data, 'acc_families'))
+    table_columns = ['Accelerator model'] + sites + ['Accelerators total']
+    generated_content += "\n== Accelerator models ==\n"
+    generated_content += MW.generate_table(table_options, table_columns, get_table_data(data, 'acc_models'))
+    generated_content += "\n== Accelerator cores ==\n"
+    table_columns = ['Accelerator model'] + sites + ['Cores total']
+    generated_content += MW.generate_table(table_options, table_columns, get_table_data(data, 'acc_cores'))
+    
+    generated_content += "\n= Nodes models =\n"
+    table_columns = ['Nodes model'] + sites + ['Nodes total']
+    generated_content += MW.generate_table(table_options, table_columns, get_table_data(data, 'node_models'))
+  end
+
+  def init(data, key1, key2)
+    if not data[key1].key?(key2)
+      data[key1][key2] = {}
+      @site_uids.each { |s| data[key1][key2][s] = 0 }
+    end
+  end
+
+  # This method generates a wiki table from data[key] values, sorted by key
+  # values in first column.
+  def get_table_data(data, key)
+    raw_data = []
+    table_data = []
+    index = 0
+    k0 = 0
+    data[key].sort_by{
+      # Sort the table by first column (= first elt of k)
+      |k, v| k[0].kind_of?(Hash) ? k[0][:sort] : k[0]
+    }.to_h.each { |k, v|
+      k0 = k if index == 0
+      index += 1
+      elts = v.sort.to_h.values
+      raw_data << elts
+      table_data << k.map{ |e| e.kind_of?(Hash) ? "data-sort-value=\"#{e[:sort]}\"|#{e[:text]}" : "data-sort-value=\"#{index.to_s.rjust(3, '0')}\"|#{e}" } +
+        elts.map{ |e| e.kind_of?(Hash) ? "data-sort-value=\"#{e[:sort]}\"|#{e[:text]}" : e }
+        .map{ |e| e == 0 ? '' : e  } + ["'''#{elts.reduce(:+)}'''"]
+    }
+    elts = raw_data.transpose.map{ |e| e.reduce(:+)}
+    table_data << {columns: ["'''Sites total'''"] +
+                   [' '] * (k0.length - 1) +
+                   (elts + [elts.reduce(:+)]).map{ |e| e == 0 ? '' : "'''#{e}'''" },
+                   sort: false}
+  end
+
+  # See: https://en.wikipedia.org/wiki/List_of_Intel_Xeon_microprocessors
+  # For a correct sort of the column, all dates must be in the same
+  # format (same number of digits)
+  def get_date(microarchitecture)
+    release_dates = {
+      'K8' => '2003',
+      'K10' => '2007',
+      'Clovertown' => '2006',
+      'Harpertown' => '2007',
+      'Dunnington' => '2008',
+      'Lynnfield' => '2009',
+      'Nehalem' => '2010',
+      'Westmere' => '2011',
+      'Sandy Bridge' => '2012',
+      'Haswell' => '2013',
+      'Broadwell' => '2015',
+    }
+    date = release_dates[microarchitecture.to_s]
+    raise 'ERROR: microarchitecture not found' if date.nil?
+    date
+  end
+  
+  def generate_interfaces
+    table_data = []
+    @global_hash["sites"].each { |site_uid, site_hash|
+      site_hash.fetch("clusters").each { |cluster_uid, cluster_hash|
+        network_interfaces = {}
+        cluster_hash.fetch('nodes').sort.each { |node_uid, node_hash|
+          next if node_hash['status'] == 'retired'
+          if node_hash['network_adapters']
+            node_interfaces = node_hash['network_adapters'].select{ |k, v| v['interface'] == 'Ethernet' and v['enabled'] == true and (v['mounted'] == true or v['mountable'] == true) and v['management'] == false }
+
+            interfaces = {}
+            interfaces['10g_count'] = node_interfaces.select { |k, v| v['rate'] == 10_000_000_000 }.count
+            interfaces['1g_count'] = node_interfaces.select { |k, v| v['rate'] == 1_000_000_000 }.count
+            interfaces['details'] = node_interfaces.map{ |k, v| k + (v['name'].nil? ? '' : '/' + v['name'])  + ' (' + G5K.get_rate(v['rate']) + ')' }.sort.join(', ')
+            queues = cluster_hash['queues'] - ['admin', 'default']
+            interfaces['queues'] = (queues.nil? || (queues.empty? ? '' : queues[0] + G5K.pluralize(queues.count, ' queue')))
+            add(network_interfaces, node_uid, interfaces) if node_interfaces.count > 1
+          end
+        }
+
+        # One line for each group of nodes with the same interfaces
+        network_interfaces.sort.to_h.each { |num, interfaces|
+          table_data << [
+            "[[#{site_uid.capitalize}:Network|#{site_uid.capitalize}]]",
+            "[[#{site_uid.capitalize}:Hardware##{cluster_uid}" + (interfaces['queues'] == '' ? '' : "_.28#{queues.gsub(' ', '_')}.29") + "|#{cluster_uid}" + (network_interfaces.size==1 ? '' : '-' + G5K.nodeset(num)) + "]]",
+            num.count,
+            interfaces['10g_count'].zero? ? '' : interfaces['10g_count'],
+            interfaces['1g_count'].zero? ? '' : interfaces['1g_count'],
+            interfaces['details']
+          ]
+        }
+      }
+    }
+    # Sort by site and cluster name
+    table_data.sort_by! { |row|
+      [row[0], row[1]]
+    }
+
+    table_options = 'class="wikitable sortable" style="text-align: center;"'
+    table_columns = ["Site", "Cluster", "Nodes", "10G interfaces", "1G interfaces", "Interfaces (throughput)"]
+    MW.generate_table(table_options, table_columns, table_data)    
+  end
+
+  # This methods adds the array interfaces to the hash
+  # network_interfaces. If nodes 2,3,7 have the same interfaces, they
+  # will be gathered in the same key and we will have
+  # network_interfaces[[2,3,7]] = interfaces
+  def add(network_interfaces, node_uid, interfaces)
+    num1 = node_uid.split('-')[1].to_i
+    if network_interfaces.has_value?(interfaces) == false
+      network_interfaces[[num1]] = interfaces
+    else
+      num2 = network_interfaces.key(interfaces)
+      network_interfaces.delete(num2)
+      network_interfaces[num2.push(num1)] = interfaces
+    end
+  end  
+end
+
+generator = G5KHardwareGenerator.new("Hardware")
+
+options = WikiGenerator::parse_options
+if (options)
+  ret = 2
+  begin
+    ret = WikiGenerator::exec(generator, options)
+  rescue StandardError => e
+    puts "Error with node: #{generator.instance_variable_get(:@node)}"
+    puts e, e.backtrace
+    ret = 3
+  ensure
+    exit(ret)
+  end
+end
-- 
GitLab