Project

General

Profile

« Previous | Next » 

Revision d9a2ebac

Added by Ohad Levy almost 12 years ago

  • ID d9a2ebac6e9ec0082e60265b794f54a29a1f7e65

[SQL optimizations] - many small optimizations

This is one of a few patches aim to improve Foreman performance
  • Bookmarks have only users, not usergroups
  • added caching to the settings table
  • removed notice message lookups (we are not really using those)
  • added caching to top bar (tabs + bookmarks) and expiry.
  • removed non needed JS to load

View differences:

app/controllers/application_controller.rb
before_filter :require_ssl, :require_login
before_filter :session_expiry, :update_activity_time, :unless => proc {|c| c.remote_user_provided? || c.api_request? } if SETTINGS[:login]
before_filter :welcome, :detect_notices, :only => :index, :unless => :api_request?
before_filter :welcome, :only => :index, :unless => :api_request?
before_filter :authorize
cache_sweeper :topbar_sweeper
def welcome
@searchbar = true
klass = controller_name == "dashboard" ? "Host" : controller_name.camelize.singularize
......
end
def update_activity_time
session[:expires_at] = Setting.idle_timeout.minutes.from_now.utc
session[:expires_at] = Setting[:idle_timeout].minutes.from_now.utc
end
def expire_session
......
logger.debug exception.backtrace.join("\n")
render :template => "common/500", :layout => !request.xhr?, :status => 500, :locals => { :exception => exception}
end
end
app/controllers/domains_controller.rb
def index
values = Domain.search_for(params[:search], :order => params[:order])
respond_to do |format|
format.html { @domains = values.paginate :page => params[:page], :include => 'hosts' }
format.html do
@domains = values.paginate :page => params[:page]
@counter = Host.count(:group => :domain_id, :conditions => {:domain_id => @domains})
end
format.json { render :json => values }
end
end
app/controllers/environments_controller.rb
def index
values = Environment.search_for(params[:search], :order => params[:order])
respond_to do |format|
format.html { @environments = values.paginate :page => params[:page] }
format.html do
@environments = values.paginate :page => params[:page]
@counter = Host.count(:group => :environment_id, :conditions => {:environment_id => @environments})
end
format.json { render :json => values.as_json }
end
end
app/controllers/models_controller.rb
def index
values = Model.search_for(params[:search], :order => params[:order])
respond_to do |format|
format.html { @models = values.paginate :page => params[:page] }
format.html do
@models = values.paginate :page => params[:page]
@counter = Host.count(:group => :model_id, :conditions => {:model_id => @models})
end
format.json { render :json => values }
end
end
app/controllers/puppetclasses_controller.rb
respond_to do |format|
format.html do
@puppetclasses = values.paginate :page => params[:page], :include => [:environments, :hostgroups]
@counter = Host.count(:group => :puppetclass_id, :joins => :puppetclasses, :conditions => {:puppetclasses => {:id => @puppetclasses}})
@host_counter = Host.count(:group => :puppetclass_id, :joins => :puppetclasses, :conditions => {:puppetclasses => {:id => @puppetclasses}})
@keys_counter = LookupKey.count(:group => :puppetclass_id, :conditions => {:puppetclass_id => @puppetclasses})
end
format.json { render :json => Puppetclass.classes2hash(values.all(:select => "name, id")) }
end
app/controllers/smart_proxies_controller.rb
class SmartProxiesController < ApplicationController
def index
respond_to do |format|
format.html {@proxies = SmartProxy.paginate :page => params[:page]}
format.html {@proxies = SmartProxy.includes(:features).paginate :page => params[:page]}
format.json {render :json => SmartProxy.all}
end
end
app/controllers/topbar_sweeper.rb
class TopbarSweeper < ActionController::Caching::Sweeper
observe [Bookmark, User, UserRole] # This sweeper is going to keep an eye on the Bookmark model
def after_create(record)
expire_cache_for(record)
end
def after_update(record)
expire_cache_for(record)
end
def after_destroy(record)
expire_cache_for(record)
end
private
def expire_cache_for(record)
expire_fragment("tabs_and_title_records_for_#{User.current}")
end
end
app/controllers/users_controller.rb
class UsersController < ApplicationController
include Foreman::Controller::AutoCompleteSearch
skip_before_filter :require_login, :only => [:login, :logout]
skip_before_filter :authorize, :only => [:login, :logout]
skip_before_filter :session_expiry, :update_activity_time, :only => [:login, :logout]
after_filter :update_activity_time, :only => :login
skip_before_filter :require_login, :authorize, :session_expiry, :update_activity_time, :only => [:login, :logout]
after_filter :update_activity_time, :only => :login
attr_accessor :editing_self
app/helpers/hosts_and_hostgroups_helper.rb
ca = SmartProxy.joins(:features).where(:features => { :name => "Puppet CA" })
proxies = SmartProxy.joins(:features).where(:features => { :name => "Puppet" })
# do not show the ca proxy, if we have only one of those and its the same as the puppet proxy
fields = puppet_ca(f) unless ca.count == 1 and ca.map(&:id) == proxies.map(&:id)
fields = puppet_ca(f) unless ca.to_a.count == 1 and ca.map(&:id) == proxies.map(&:id)
"#{fields} #{puppet_master(f)}".html_safe
end
......
return unless SETTINGS[:unattended]
proxies = SmartProxy.joins(:features).where(:features => { :name => "Puppet CA" })
select_f f, :puppet_ca_proxy_id, proxies, :id, :name,
{ :include_blank => proxies.count > 1 },
{ :include_blank => proxies.to_a.count > 1 },
{ :label => "Puppet CA",
:help_inline => "Use this puppet server as a CA server" }
end
......
def puppet_master f
proxies = SmartProxy.joins(:features).where(:features => { :name => "Puppet" })
select_f f, :puppet_proxy_id, proxies, :id, :name,
{ :include_blank => proxies.count > 1 },
{ :include_blank => proxies.to_a.count > 1 },
{ :label => "Puppet Master",
:help_inline => "Use this puppet server as an initial Puppet Server or to execute puppet runs" }
end
app/helpers/puppetclasses_helper.rb
def host_counter klass
# workaround for sqlite bug
# https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4544-rails3-activerecord-sqlite3-lost-column-type-when-using-views#ticket-4544-2
@counter[klass.id.to_s] || @counter[klass.id.to_i] || 0
@host_counter[klass.id.to_s] || @host_counter[klass.id.to_i] || 0
rescue
"N/A"
end
app/models/bookmark.rb
return {} unless SETTINGS[:login] and !user.nil?
user = User.current
conditions = sanitize_sql_for_conditions(["((bookmarks.public = ?) OR (bookmarks.owner_id in (?) AND bookmarks.owner_type = 'Usergroup') OR (bookmarks.owner_id = ? AND bookmarks.owner_type = 'User'))", true, user.my_usergroups.map(&:id), user.id])
conditions = sanitize_sql_for_conditions(["((bookmarks.public = ?) OR (bookmarks.owner_id = ? AND bookmarks.owner_type = 'User'))", true, user.id])
{:conditions => conditions}
}
app/models/environment.rb
end
def find_import_proxies
if (f = Feature.where(:name => "Puppet"))
if !f.empty? and (proxies=f.first.smart_proxies)
return proxies
end
end
[]
SmartProxy.joins(:features).where(:features => {:name => 'Puppet'})
end
end
app/models/host.rb
# returns sorted hash
def self.count_habtm association
output = {}
Host.count(:include => association.pluralize, :group => "#{association}_id").to_a.each do |a|
#Ugly Ugly Ugly - I guess I'm missing something basic here
if a[0]
label = eval(association.camelize).send("find",a[0].to_i).to_label
output[label] = a[1]
end
end
output
counter = Host.count(:include => association.pluralize, :group => "#{association}_id")
# returns {:id => count...}
#Puppetclass.find(counter.keys.compact)...
Hash[eval("#{association.camelize}.find(#{counter.keys.compact.join(',')})").map {|i| [i.to_label, counter[i.id]]}]
end
def resources_chart(timerange = 1.day.ago)
app/models/setting.rb
def self.per_page; 20 end # can't use our own settings
def self.[](name)
if (record = first(:conditions => { :name => name.to_s }))
record.value
name = name.to_s
cache.fetch name do
where(:name => name).first.try(:value)
end
end
def self.[]=(name, value)
record = find_or_create_by_name name.to_s
name = name.to_s
record = find_or_create_by_name name
record.value = value
cache.delete(name)
record.save!
end
......
def value= val
v = (val.nil? or val == default) ? nil : val.to_yaml
self.class.cache.delete(name.to_s)
write_attribute :value, v
end
......
private
def self.cache
Rails.cache
end
def save_as_settings_type
return true unless settings_type.nil?
t = default.class.to_s.downcase
app/models/user.rb
belongs_to :auth_source
has_many :auditable_changes, :class_name => '::Audit', :as => :user
has_many :usergroup_member, :as => :member
has_many :usergroups, :through => :usergroup_member
has_many :direct_hosts, :as => :owner, :class_name => "Host"
has_and_belongs_to_many :notices, :join_table => 'user_notices'
......
def allowed_to?(action, options={})
return true if admin?
return true if editing_self
return false if roles.empty?
roles.detect {|role| role.allowed_to?(action)}.present?
end
app/views/domains/index.html.erb
<% for domain in @domains %>
<tr class="<%= cycle("even", "odd") -%>">
<td><%= link_to_if_authorized h(domain.fullname.empty? ? domain.name : domain.fullname), hash_for_edit_domain_path(:id => domain.name)%></td>
<td><%= link_to_if domain.hosts.any?, domain.hosts.size, hosts_path(:search => "domain = #{domain}") %>
<td><%= link_to @counter[domain.id] || 0, hosts_path(:search => "domain = #{domain}") %>
<td><%= display_delete_if_authorized hash_for_domain_path(:id => domain), :confirm => "Delete #{domain.name}?" %></td>
</tr>
<% end %>
app/views/environments/index.html.erb
<td>
<%= link_to_if_authorized h(environment.name), hash_for_edit_environment_path(:id=> environment.name) %>
</td>
<td><%= link_to_if environment.hosts.any?, environment.hosts.size, hosts_path(:search => "environment = #{environment}") %>
<td><%= link_to @counter[environment.id] || 0, hosts_path(:search => "environment = #{environment}") %>
<td>
<%= action_buttons(link_to("Classes", puppetclasses_path(:search => "environment = #{environment}"), :class=>'btn btn-small'),
display_delete_if_authorized(hash_for_environment_path(:id => environment.name), :confirm => "Delete #{environment.name}?")) %>
app/views/home/_topbar.rhtml
<%=image_tag("foreman.png", :class=>"logo") %><a class="brand logo-text" href="#">Foreman</a>
<!-- menu -->
<% if User.current -%>
<div class="nav-collapse nav1">
<ul class="nav" id="menu">
<% my_bookmarks = Bookmark.my_bookmarks %>
<% if User.current -%>
<% cache("tabs_and_title_bookmarks_for_#{User.current}") do %>
<div class="nav-collapse nav1">
<ul class="nav" id="menu">
<% my_bookmarks = Bookmark.my_bookmarks %>
<%= menu 'dashboard', my_bookmarks %>
<%= menu 'hosts', my_bookmarks %>
<%= menu 'reports', my_bookmarks, hash_for_reports_path.merge(:search => 'eventful = true') %>
<%= menu 'facts' , my_bookmarks, hash_for_fact_values_path %>
<%= menu 'audits', my_bookmarks, hash_for_audits_path %>
<%= menu 'statistics', my_bookmarks %>
</ul>
</div>
<div class="nav-collapse nav2">
<ul class="nav pull-right" id="menu2">
<%= render "home/settings" -%>
<% if SETTINGS[:login]%>
<%= menu 'dashboard', my_bookmarks %>
<%= menu 'hosts', my_bookmarks %>
<%= menu 'reports', my_bookmarks, hash_for_reports_path.merge(:search => 'eventful = true') %>
<%= menu 'facts' , my_bookmarks, hash_for_fact_values_path %>
<%= menu 'audits', my_bookmarks, hash_for_audits_path %>
<%= menu 'statistics', my_bookmarks %>
</ul>
</div>
<div class="nav-collapse nav2">
<ul class="nav pull-right" id="menu2">
<%= render "home/settings" -%>
<% if SETTINGS[:login]%>
<%= render 'home/user_dropdown' %>
<% end -%>
</ul>
</div>
<% end -%>
</ul>
</div>
<% end -%>
<% end -%>
app/views/layouts/application.html.erb
<title> <%= h(yield(:title) || "Foreman") %></title>
<%= stylesheet_link_tag 'jquery-ui', 'jquery.jnotify', 'bootstrap.min', 'style', 'bootstrap-responsive.min' %>
<%= javascript_include_tag :defaults, 'jquery_ujs', 'jquery-ui', 'highcharts', 'charts', 'jquery.jnotify', 'jquery.jeditable',
'bootstrap.min', 'bootstrap-dropdown', 'bootstrap-alert', 'bootstrap-tab', 'bootstrap-modal', 'bootstrap-tooltip', 'bootstrap-popover', 'bootstrap-collapse' %>
'bootstrap.min' %>
<%= csrf_meta_tag %>
<%= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery? %>
<meta name="apple-mobile-web-app-capable" content="yes">
app/views/models/index.html.erb
<td><%=link_to_if_authorized h(model.name), hash_for_edit_model_path(:id => model)%></td>
<td><%=h(model.vendor_class)%></td>
<td><%=h(model.hardware_model)%></td>
<td class="ra"><%= link_to_if model.hosts.any?, model.hosts.count, hosts_path(:search=>"model = \"#{model.name}\"") %></td>
<td class="ra"><%= link_to @counter[model.id] || 0, hosts_path(:search => "model = \"#{model}\"") %></td>
<td class="ra">
<%= display_delete_if_authorized hash_for_model_path(:id => model), :confirm => "Delete #{model.name}?" %>
</td>
app/views/puppetclasses/index.html.erb
</td>
<td><%= puppetclass.hostgroups.map {|hg| link_to_if_authorized hg, hash_for_edit_hostgroup_path(:id=>hg)}.to_sentence.html_safe %></td>
<td> <%= link_to host_counter(puppetclass), hosts_path(:search => "class = #{puppetclass.name}")%></td>
<td>
<%= link_to puppetclass.lookup_keys.count, puppetclass_lookup_keys_path(puppetclass) %>
</td>
<td><%= link_to @keys_counter[puppetclass.id] || 0, puppetclass_lookup_keys_path(puppetclass) %> </td>
<td>
<%= display_delete_if_authorized hash_for_puppetclass_path(:id => puppetclass), :confirm => "Delete #{puppetclass.name}?" %>
</td>
app/views/smart_proxies/index.html.erb
<td><%=h proxy.url %></td>
<td><%=h proxy.features.to_sentence %></td>
<td>
<% ca = proxy.features.include? Feature.find_by_name("Puppet CA") %>
<% ca = proxy.features.detect{|f| f.name == "Puppet CA"} %>
<%= action_buttons(
if ca
display_link_if_authorized("Certificates", hash_for_smart_proxy_puppetca_index_path(:smart_proxy_id => proxy), :class=>"btn")
......
if ca
display_link_if_authorized("Autosign", hash_for_smart_proxy_autosign_index_path(:smart_proxy_id => proxy))
end,
if SETTINGS[:unattended] and proxy.features.include? Feature.find_by_name("DHCP")
if SETTINGS[:unattended] and proxy.features.detect{|f| f.name == "DHCP" }
display_link_if_authorized("Import Subnets", hash_for_import_subnets_path(:smart_proxy_id => proxy),:class => ca ? "" : "btn")
end,
display_delete_if_authorized(hash_for_smart_proxy_path(:id => proxy), :confirm => "Delete #{proxy.name}?"))%>
bundler.d/development.rb
gem 'single_test'
gem 'pry'
gem "term-ansicolor"
gem 'rack-mini-profiler'
end
test/unit/setting_test.rb
require 'test_helper'
class SettingTest < ActiveSupport::TestCase
def setup
Setting.cache.clear
end
# commenting out due a failure in our CI
# def test_settings_should_save_complex_types
# assert (Setting.create(:name => "foo", :value => [1,2,3,'b'], :default => ['b',"b"], :description => "test foo" ))
......
end
def test_boolean_values_should_have_setting_type_for_false
assert Setting.create(:name => "oo", :default => false, :description => "test foo")
assert Setting.create!(:name => "oo", :default => false, :description => "test foo")
assert_equal "boolean", Setting.find_by_name("oo").settings_type
assert_equal false, Setting["oo"]
end

Also available in: Unified diff