forked from theforeman/foreman
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #21776 - Improved fact importing to deal with names
Now the list of fact names will be calculated beforehand and saved to the database. It will also consider that the name could be added from other thread.
- Loading branch information
1 parent
0eac53b
commit c7c53ae
Showing
12 changed files
with
286 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,72 @@ | ||
class StructuredFactImporter < FactImporter | ||
def normalize(facts) | ||
# Remove empty values first, so nil facts added by normalize_recurse imply compose | ||
# Remove empty values first, so nil facts added by flatten_composite imply compose | ||
facts = facts.select { |k, v| v.present? } | ||
normalize_recurse({}, facts) | ||
facts = flatten_composite({}, facts) | ||
|
||
original_keys = facts.keys.to_a | ||
original_keys.each do |key| | ||
fill_hierarchy(facts, key) | ||
end | ||
|
||
facts | ||
end | ||
|
||
# expand {'a' => {'b' => 'c'}} to {'a' => nil, 'a::b' => 'c'} | ||
def normalize_recurse(memo, facts, prefix = '') | ||
def flatten_composite(memo, facts, prefix = '') | ||
facts.each do |k, v| | ||
k = prefix.empty? ? k.to_s : prefix + FactName::SEPARATOR + k.to_s | ||
if v.is_a?(Hash) | ||
memo[k] = nil | ||
normalize_recurse(memo, v, k) | ||
flatten_composite(memo, v, k) | ||
else | ||
memo[k] = v.to_s | ||
end | ||
end | ||
memo | ||
end | ||
|
||
# ensures that parent facts already exist in the hash. | ||
# Example: for fact: "a::b::c", it will make sure that "a" and "a::b" exist in | ||
# the hash. | ||
def fill_hierarchy(facts, child_fact_name) | ||
facts[child_fact_name] = nil unless facts.key?(child_fact_name) | ||
|
||
parent_name = parent_fact_name(child_fact_name) | ||
fill_hierarchy(facts, parent_name) if parent_name | ||
end | ||
|
||
def preload_fact_names | ||
# Also fetch compose values, generating {NAME => [ID, COMPOSE]}, avoiding loading the entire model | ||
Hash[fact_name_class.where(:type => fact_name_class).reorder('').pluck(:name, :id, :compose).map { |fact| [fact.shift, fact] }] | ||
end | ||
|
||
def find_or_create_fact_name(name, value = nil) | ||
if name.include?(FactName::SEPARATOR) | ||
parent_name = /(.*)#{FactName::SEPARATOR}/.match(name)[1] | ||
parent_fact = find_or_create_fact_name(parent_name, nil) | ||
fact_name = parent_fact.children.where(:name => name, :type => fact_name_class.to_s).first | ||
else | ||
parent_fact = nil | ||
fact_name = fact_name_class.where(:name => name, :type => fact_name_class.to_s).first | ||
end | ||
def ensure_fact_names | ||
super | ||
|
||
if fact_name | ||
fact_name.update_attribute(:compose, value.nil?) if value.nil? && !fact_name.compose? | ||
else | ||
fact_name = fact_name_class.create!(:name => name, :type => fact_name_class.to_s, :compose => value.nil?, :parent => parent_fact) | ||
end | ||
fact_name | ||
composite_fact_names = facts.map do |key, value| | ||
key if value.nil? | ||
end.compact | ||
|
||
affected_records = fact_name_class.where(:name => composite_fact_names, :compose => false).update_all(:compose => true) | ||
|
||
# reload name records if compose flag was reset. | ||
initialize_fact_names if affected_records > 0 | ||
end | ||
|
||
def fact_name_attributes(name) | ||
attributes = super | ||
fact_value = facts[name] | ||
parent_fact_record = fact_names[parent_fact_name(name)] | ||
|
||
attributes[:parent] = parent_fact_record | ||
attributes[:compose] = fact_value.nil? | ||
attributes | ||
end | ||
|
||
def parent_fact_name(child_fact_name) | ||
split = child_fact_name.rindex(FactName::SEPARATOR) | ||
return nil unless split | ||
child_fact_name[0, split] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
require "benchmark/benchmark_helper" | ||
require 'memory_profiler' | ||
require "stackprof" | ||
|
||
def with_memory_profiler | ||
report = MemoryProfiler.report do | ||
yield | ||
end | ||
report.pretty_print | ||
end | ||
|
||
def with_stackprof | ||
StackProf.run(mode: :object, raw: true, out: '/tmp/stackprof_objects.dump', interval: 1) do | ||
yield | ||
end | ||
puts '/tmp/stackprof_objects.dump dump created, please use "stackprof --text /tmp/stackprof_objects.dump" to investigate' | ||
end | ||
|
||
def with_chosen_profiler(&block) | ||
case (ENV['PROFILER'] || '').downcase | ||
when 'memory_profiler' | ||
profiler_method = :with_memory_profiler | ||
when 'stackprof' | ||
profiler_method = :with_stackprof | ||
else | ||
puts 'Set "PROFILER" to either "memory_profiler" or "stackprof"' | ||
exit 1 | ||
end | ||
|
||
send(profiler_method, &block) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# frozen_string_literal: true | ||
|
||
require "benchmark/memory/memory_benchmark_helper" | ||
|
||
FactName.transaction do | ||
(0..100000).each do |x| | ||
FactName.connection.execute "INSERT INTO fact_names (name) values ('rand_fact_name_#{x}')" | ||
end | ||
end | ||
|
||
class StructuredFactImporter | ||
def fact_name_class | ||
FactName | ||
end | ||
end | ||
|
||
def generate_facts(total, unique_names = 0, structured_names = 0) | ||
facts = Hash[(1..total).map{|i| ["fact_#{i}", "value_#{i}"]}] | ||
(total..total+unique_names).map{|i| facts["fact_#{i}_#{Foreman.uuid}"] = "value_#{i}"} | ||
(total..total+structured_names).map{|i| facts[(["f#{i}"] * (i % 10)).join('::') + i.to_s] = "value_#{i}"} | ||
facts | ||
end | ||
|
||
Rails.logger.level = Logger::ERROR | ||
|
||
facts = generate_facts(200, 50, 25) | ||
User.current = User.unscoped.find(1) | ||
|
||
host = FactoryBot.create(:host, :name => "benchmark-#{Foreman.uuid}") | ||
|
||
with_chosen_profiler do | ||
StructuredFactImporter.new(host, facts).import! | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Create notification blueprints prior to tests | ||
module FactImporterIsolation | ||
extend ActiveSupport::Concern | ||
|
||
def allow_transactions_for(importer) | ||
importer.stubs(:ensure_no_active_transaction).returns(true) | ||
end | ||
|
||
module ClassMethods | ||
def allow_transactions_for_any_importer | ||
FactImporter.singleton_class.prepend FactImporterFactoryStubber | ||
|
||
FactImporter.register_instance_stubs do |importer_class| | ||
importer_class.any_instance.stubs(:ensure_no_active_transaction).returns(true) | ||
end | ||
end | ||
end | ||
end | ||
|
||
module FactImporterFactoryStubber | ||
def register_instance_stubs(&block) | ||
instance_stubs << block | ||
end | ||
|
||
def importer_for(*args) | ||
instance = super | ||
instance_stubs.each do |stub_block| | ||
stub_block.call(instance) | ||
end | ||
instance | ||
end | ||
|
||
def instance_stubs | ||
@instance_stubs ||= [] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.