app/models/metric/processing.rb
module Metric::Processing
DERIVED_COLS = [
:derived_cpu_available,
:derived_cpu_reserved,
:derived_host_count_off,
:derived_host_count_on,
:derived_host_count_total,
:derived_memory_available,
:derived_memory_reserved,
:derived_memory_used,
:derived_host_sockets,
:derived_vm_allocated_disk_storage,
:derived_vm_count_off,
:derived_vm_count_on,
:derived_vm_count_total,
:derived_vm_numvcpus, # TODO: This is cpu_total_cores and needs to be renamed, but reports depend on the name :numvcpus
# See also #TODO on VimPerformanceState.capture
:derived_vm_used_disk_storage,
# TODO(lsmola) as described below, this field should be named derived_cpu_used
:cpu_usagemhz_rate_average
]
VALID_PROCESS_TARGETS = [
VmOrTemplate,
Container,
ContainerGroup,
ContainerNode,
ContainerProject,
ContainerReplicator,
ContainerService,
ContainerImage,
Host,
AvailabilityZone,
HostAggregate,
EmsCluster,
ExtManagementSystem,
MiqRegion,
MiqEnterprise,
Service
]
def self.process_derived_columns(obj, attrs, ts = nil)
unless VALID_PROCESS_TARGETS.any? { |t| obj.kind_of?(t) }
raise _("object %{name} is not one of %{items}") % {:name => obj,
:items => VALID_PROCESS_TARGETS.collect(&:name).join(", ")}
end
ts = attrs[:timestamp] if ts.nil?
state = obj.vim_performance_state_for_ts(ts)
total_cpu = state.total_cpu || 0
total_mem = state.total_mem || 0
result = {}
have_cpu_metrics = attrs[:cpu_usage_rate_average] || attrs[:cpu_usagemhz_rate_average]
have_mem_metrics = attrs[:mem_usage_absolute_average] || attrs[:derived_memory_used]
DERIVED_COLS.each do |col|
_dummy, group, typ, mode = col.to_s.split("_")
next if group == "vm" && obj.kind_of?(Service) && typ != "count"
case typ
when "available"
# Do not derive "available" values if there haven't been any usage
# values collected
if group == "cpu"
result[col] = total_cpu if have_cpu_metrics && total_cpu > 0
else
result[col] = total_mem if have_mem_metrics && total_mem > 0
end
when "allocated"
method = col.to_s.split("_")[1..-1].join("_")
result[col] = state.send(method) if state.respond_to?(method)
when "used"
if group == "cpu"
# TODO: This branch is never called because there isn't a column
# called derived_cpu_used. The callers, such as chargeback, generally
# use cpu_usagemhz_rate_average directly, and the column may not be
# needed, but perhaps should be added to normalize like is done for
# memory. The derivation here could then use cpu_usagemhz_rate_average
# directly if avaiable, otherwise do the calculation below.
result[col] = (attrs[:cpu_usage_rate_average] / 100 * total_cpu) unless total_cpu == 0 || attrs[:cpu_usage_rate_average].nil?
elsif group == "memory"
if attrs[:mem_usage_absolute_average].nil?
# If we can't get percentage usage, just used RAM in MB, lets compute percentage usage
attrs[:mem_usage_absolute_average] = 100.0 / total_mem * attrs[:derived_memory_used] if total_mem > 0 && !attrs[:derived_memory_used].nil?
else
# We have percentage usage of RAM, lets compute consumed RAM in MB
result[col] = (attrs[:mem_usage_absolute_average] / 100 * total_mem) unless total_mem == 0 || attrs[:mem_usage_absolute_average].nil?
end
else
method = col.to_s.split("_")[1..-1].join("_")
result[col] = state.send(method) if state.respond_to?(method)
end
when "rate"
if col.to_s == "cpu_usagemhz_rate_average" && attrs[:cpu_usagemhz_rate_average].blank?
# TODO(lsmola) for some reason, this column is used in chart, although from processing code above, it should
# be named derived_cpu_used. Investigate what is the right solution and make it right. For now lets fill
# the column shown in charts.
result[col] = (attrs[:cpu_usage_rate_average] / 100 * total_cpu) unless total_cpu == 0 || attrs[:cpu_usage_rate_average].nil?
end
when "reserved"
method = group == "cpu" ? :reserve_cpu : :reserve_mem
result[col] = state.send(method)
when "count"
method = [group, typ, mode].join("_")
result[col] = state.send(method)
when "numvcpus" # This is actually logical cpus. See note above.
# Do not derive "available" values if there haven't been any usage
# values collected
result[col] = state.numvcpus if have_cpu_metrics && state.try(:numvcpus).to_i > 0
when "sockets"
result[col] = state.host_sockets
end
end
result[:assoc_ids] = state.assoc_ids
result[:tag_names] = state.tag_names
result[:parent_host_id] = state.parent_host_id
result[:parent_storage_id] = state.parent_storage_id
result[:parent_ems_id] = state.parent_ems_id
result[:parent_ems_cluster_id] = state.parent_ems_cluster_id
result
end
def self.add_missing_intervals(obj, interval_name, start_time, end_time)
klass, meth = Metric::Helper.class_and_association_for_interval_name(interval_name)
scope = obj.send(meth).for_time_range(start_time, end_time)
scope = scope.where(:capture_interval_name => interval_name) if interval_name != "realtime"
extrapolate(klass, scope)
end
def self.extrapolate(klass, scope)
last_perf = {}
scope.order("timestamp, capture_interval_name").each do |perf|
interval = interval_name_to_interval(perf.capture_interval_name)
last_perf[interval] = perf if last_perf[interval].nil?
if (perf.timestamp - last_perf[interval].timestamp) <= interval
last_perf[interval] = perf
next
end
new_perf = create_new_metric(klass, last_perf[interval], perf, interval)
new_perf.save!
last_perf[interval] = perf
end
end
def self.create_new_metric(klass, last_perf, perf, interval)
attrs = last_perf.attributes
attrs.delete('id')
attrs['timestamp'] += interval
attrs['capture_interval'] = 0
new_perf = klass.new(attrs)
Metric::Rollup::ROLLUP_COLS.each do |c|
next if new_perf.send(c).nil? || perf.send(c).nil?
new_perf.send(c.to_s + "=", (new_perf.send(c) + perf.send(c)) / 2)
end
unless perf.assoc_ids.nil?
Metric::Rollup::ASSOC_KEYS.each do |assoc|
next if new_perf.assoc_ids.nil? || new_perf.assoc_ids[assoc].blank? || perf.assoc_ids[assoc].blank?
new_perf.assoc_ids[assoc][:on] ||= []
new_perf.assoc_ids[assoc][:off] ||= []
new_perf.assoc_ids[assoc][:on] = (new_perf.assoc_ids[assoc][:on] + perf.assoc_ids[assoc][:on]).uniq!
new_perf.assoc_ids[assoc][:off] = (new_perf.assoc_ids[assoc][:off] + perf.assoc_ids[assoc][:off]).uniq!
end
end
new_perf
end
private_class_method :extrapolate, :create_new_metric
def self.interval_name_to_interval(name)
case name
when "realtime" then 20
when "hourly" then 1.hour.to_i
when "daily" then 1.day.to_i
else raise _("unknown interval name: [%{name}]") % {:name => name}
end
end
end