#!/usr/bin/env ruby # Build yaml files which provide arguments passed to {lv,pv,vg}s and subsequent # type conversions. # # ./generate_field_data ~/LVM2.2.02.38 require "fileutils" require "yaml" VERSION_FILE = "/VERSION".freeze COLUMNS_FILE = "/lib/report/columns.h".freeze debug = false TYPE_CONVERSION_MAP = { # Only types we can really trust "uint32" => "Integer", "int32" => "Integer", # These were determined by reading the code, they invoke _(u)int32_disp right away "pvmdas" => "Integer", "vgmdas" => "Integer", "lvcount" => "Integer", "lvsegcount" => "Integer", "segstartpe" => "Integer", # listed to return STR? "lvkmaj" => "Integer", "lvkmin" => "Integer", "snpercent" => "Float", "copypercent" => "Float", # size32/64, these do unit formatting unless overridden on command line. We # typically want them in bytes so we can convert them to Integers safely "size32" => "Integer", "size64" => "Integer", # These types return size32/size64 as well "lvkreadahead" => "Integer", "pvsize" => "Integer", "devsize" => "Integer", "originsize" => "Integer", "pvfree" => "Integer", "pvused" => "Integer", "pvmdafree" => "Integer", "pvmdasize" => "Integer", "vgsize" => "Integer", "vgfree" => "Integer", "vgmda_free" => "Integer", "chunksize" => "Integer", "segstart" => "Integer", "segsize" => "Integer", "snapcount" => "Integer", "vgmdafree" => "Integer", "vgmdasize" => "Integer", "pvextvsn" => "Integer", "vgmissingpvcount" => "Integer", # Weird one, can be "auto" or size32 "lvreadahead" => "String", "lvmetadatasize" => "Integer", "datapercent" => "Float", "metadatapercent" => "Float", "pvmdasused" => "Integer", "vgmdasused" => "Integer", "vgmdacopies" => "Integer", "thincount" => "Integer", "thinid" => "Integer", "discards" => "Integer", "thinzero" => "Integer", "transactionid" => "Integer", "raidmismatchcount" => "Integer", "raidwritebehind" => "Integer", "raidminrecoveryrate" => "Integer", "raidmaxrecoveryrate" => "Integer", "segsizepe" => "Integer", # New fields for caching "cache_total_blocks" => "Integer", "cache_used_blocks" => "Integer", "cache_dirty_blocks" => "Integer", "cache_read_hits" => "Integer", "cache_read_misses" => "Integer", "cache_write_hits" => "Integer", "cache_write_misses" => "Integer", # 2.02.169+ "lv_size" => "Integer", "kernelmetadataformat" => "Integer", "seg_stripes" => "Integer", "seg_data_stripes" => "Integer", "seg_reshape_len" => "Integer", "seg_reshape_len_le" => "Integer", "seg_data_copies" => "Integer", "seg_data_offset" => "Integer", "seg_new_data_offset" => "Integer", "seg_parity_chunks" => "Integer", "cachemetadataformat" => "Integer", }.freeze lvm_source = ARGV[0] version = File.readlines(lvm_source + VERSION_FILE)[0].split(/-git|\s+/)[0] lvs = [] lvssegs = [] pvs = [] pvssegs = [] vgs = [] File.readlines(lvm_source + COLUMNS_FILE).each do |line| # eg: FIELD(LVS, lv, STR, "LV UUID", lvid.id[1], 38, uuid, "lv_uuid", "Unique identifier") next unless line =~ /^FIELD\((.*)\)$/ fields = $1.split(", ") fields.each { |f| f.gsub!(/^"/, ""); f.gsub!(/"$/, "") } p fields if debug app = fields[0] general_type = fields[2] specific_type = fields[6] column = fields[7] method = fields[7].dup description = fields[8] p app, general_type, specific_type, column, method, description if debug if %w{NUM SIZ}.include?(general_type) attribute_type = TYPE_CONVERSION_MAP[specific_type] if attribute_type.nil? puts "Oops, missing type conversion data of column '#{specific_type}' use by '#{app}' which says its going to return a '#{specific_type}'" puts "Figure out the missing type and rerun." exit 1 end else attribute_type = "String" end # our shorter nicer method names, according to the man page these can be # dropped when passing column names as arguments as well, but i found a few # with issues (seg_start). case app when "LVS", "LVSINFOSTATUS", "LVSSTATUS" method.sub!(/^lv_/, "") when "SEGS" method.sub!(/^seg_/, "") when "LABEL" method.sub!(/^pv_/, "") when "PVS" method.sub!(/^pv_/, "") when "PVSEGS" method.sub!(/^pvseg_/, "") when "VGS" method.sub!(/^vg_/, "") end attribute = { method: method, column: column, type_hint: attribute_type, description: description, } case app when "LVS", "LVSINFOSTATUS", "LVSSTATUS" lvs << attribute when "SEGS" lvssegs << attribute when "LABEL" pvs << attribute when "PVS" pvs << attribute when "PVSEGS" pvssegs << attribute when "VGS" vgs << attribute end end # we use vg_uuid as our crossover attribute that links vg->lv and vg->pv attribute = { method: "vg_uuid", column: "vg_uuid", type_hint: "String", description: "For VolumeGroup to LogicalVolume relationship." } lvs << attribute attribute = { method: "vg_uuid", column: "vg_uuid", type_hint: "String", description: "For VolumeGroup to PhysicalVolume relationship." } pvs << attribute # and we link lv->lvsegment, pv->pvsegment attribute = { method: "lv_uuid", column: "lv_uuid", type_hint: "String", description: "For LogicalVolume to LogicalVolumeSegment relationship." } lvssegs << attribute attribute = { method: "pv_uuid", column: "pv_uuid", type_hint: "String", description: "For PhysicalVolume to PhysicalVolumeSegment relationship." } pvssegs << attribute lvs.sort! { |x, y| x[:column] <=> y[:column] } lvssegs.sort! { |x, y| x[:column] <=> y[:column] } pvs.sort! { |x, y| x[:column] <=> y[:column] } pvssegs.sort! { |x, y| x[:column] <=> y[:column] } vgs.sort! { |x, y| x[:column] <=> y[:column] } attributes_dir = "lib/lvm/attributes/#{version}" FileUtils.mkdir(attributes_dir) disclaimer = <<~GO # These are column to object attribute mappings # generated by #{$0} based on # #{lvm_source}/lib/report/columns.h GO File.open("#{attributes_dir}/lvs.yaml", "w") { |f| f.write(disclaimer); f.write(lvs.to_yaml) } File.open("#{attributes_dir}/lvsseg.yaml", "w") { |f| f.write(disclaimer); f.write(lvssegs.to_yaml) } File.open("#{attributes_dir}/pvs.yaml", "w") { |f| f.write(disclaimer); f.write(pvs.to_yaml) } File.open("#{attributes_dir}/pvsseg.yaml", "w") { |f| f.write(disclaimer); f.write(pvssegs.to_yaml) } File.open("#{attributes_dir}/vgs.yaml", "w") { |f| f.write(disclaimer); f.write(vgs.to_yaml) } puts "Done."