Skip to content

Commit

Permalink
Merge e770db4 into 8ad630d
Browse files Browse the repository at this point in the history
  • Loading branch information
elrayle committed Jan 10, 2020
2 parents 8ad630d + e770db4 commit 7dd3cf0
Show file tree
Hide file tree
Showing 12 changed files with 212 additions and 145 deletions.
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ Metrics/BlockLength:
Exclude:
- 'qa_server.gemspec'
- 'spec/**/*.rb'

Metrics/ClassLength:
Exclude:
- 'lib/qa_server/configuration.rb'
67 changes: 25 additions & 42 deletions app/controllers/qa_server/monitor_status_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ class MonitorStatusController < QaServer::AuthorityValidationController

# Sets up presenter with data to display in the UI
def index
refresh_tests
historical_data = refresh_history
performance_data = refresh_performance
latest_run
@presenter = presenter_class.new(current_summary: latest_summary,
current_failure_data: latest_failures,
historical_summary_data: historical_data,
Expand All @@ -25,49 +23,39 @@ def index

private

# Sets @latest_run [QaServer::ScenarioRunRegistry]
def latest_run
@latest_run ||= scenario_run_registry_class.latest_run
Rails.cache.fetch("#{self.class}/#{__method__}", expires_in: QaServer.cache_expiry, race_condition_ttl: 1.hour, force: refresh_tests?) do
Rails.logger.info("#{self.class}##{__method__} - Running Tests - cache expired or refresh requested (#{refresh_tests?})")
validate(authorities_list)
scenario_run_registry_class.save_run(scenarios_results: status_log.to_a)
scenario_run_registry_class.latest_run
end
end

# Sets @latest_summary [QaServer::ScenarioRunSummary]
def latest_summary
@latest_summary ||= scenario_history_class.run_summary(scenario_run: latest_run)
scenario_history_class.run_summary(scenario_run: latest_run, force: refresh_tests?)
end

def latest_failures
@status_data ||= scenario_history_class.run_failures(run_id: latest_run.id)
end

def update_summary_and_data
scenario_run_registry_class.save_run(scenarios_results: status_log.to_a)
@latest_summary = nil # reset so next request recalculates
@latest_failures = nil # reset so next request recalculates
scenario_history_class.run_failures(run_id: latest_run.id, force: refresh_tests?)
end

def expired?
@expired ||= latest_summary.blank? || latest_summary.run_dt_stamp < QaServer.monitoring_expires_at
end

def historical_summary_data(refresh: false)
# TODO: Make this refresh the same way performance data refreshes.
# Requires historical graph to move out of presenter so it can be created here only with refresh.
if refresh
@historical_summary_data = scenario_history_class.historical_summary
# TODO: Need to recreate graph here. And need to only read the graph in presenter.
end
@historical_summary_data ||= scenario_history_class.historical_summary
# Sets @historical_data [Array<Hash>]
def historical_data
scenario_history_class.historical_summary(force: refresh_history?)
end

def performance_data(refresh: false)
datatype = performance_datatype(refresh)
return if datatype == :none
@performance_data = nil if refresh
@performance_data ||= performance_history_class.performance_data(datatype: datatype)
# Sets @performance_data [Hash<Hash>]
def performance_data
performance_history_class.performance_data(datatype: performance_datatype, force: refresh_performance?)
end

def performance_datatype(refresh) # rubocop:disable Metrics/CyclomaticComplexity
return :all if display_performance_datatable? && display_performance_graph? && refresh
def performance_datatype
return :all if display_performance_datatable? && display_performance_graph?
return :datatable if display_performance_datatable?
return :graph if display_performance_graph? && refresh
return :graph if display_performance_graph?
:none
end

Expand All @@ -79,12 +67,6 @@ def display_performance_graph?
@display_performance_graph ||= QaServer.config.display_performance_graph?
end

def refresh_tests
return unless refresh_tests?
validate(authorities_list)
update_summary_and_data
end

def refresh_history
historical_summary_data(refresh: refresh_history?)
end
Expand All @@ -98,12 +80,13 @@ def refresh?
end

def refresh_all?
return false unless refresh?
params[:refresh].nil? || params[:refresh].casecmp?('all') # nil is for backward compatibility
end

def refresh_tests?
return false unless refresh? || expired?
refresh_all? || params[:refresh].casecmp?('tests') || expired?
return false unless refresh?
refresh_all? || params[:refresh].casecmp?('tests')
end

def refresh_history?
Expand All @@ -112,8 +95,8 @@ def refresh_history?
end

def refresh_performance?
return false unless refresh? || expired?
refresh_all? || params[:refresh].casecmp?('performance') || expired?
return false unless refresh?
refresh_all? || params[:refresh].casecmp?('performance')
end
end
end
1 change: 1 addition & 0 deletions app/models/qa_server/performance_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def write_all
end

def log(id:)
return if QaServer.config.suppress_logging_performance_datails
Rails.logger.debug("*** performance data for id: #{id} ***")
Rails.logger.debug(@cache[id].to_yaml)
end
Expand Down
39 changes: 22 additions & 17 deletions app/models/qa_server/performance_history.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ def create_record(authority:, action:, dt_stamp: QaServer.current_time)
# },
# AGROVOC_LD4L_CACHE: { ... # same data for each authority }
# }
def performance_data(datatype: :datatable)
def performance_data(datatype: :datatable, force: false)
return if datatype == :none
QaServer.config.performance_cache.write_all
data = calculate_data(datatype)
data = calculate_data(datatype, force: force)
graphing_service_class.create_performance_graphs(performance_data: data) if calculate_graphdata? datatype
data
end
Expand All @@ -82,42 +82,47 @@ def calculate_graphdata?(datatype)
datatype == :graph || datatype == :all
end

def calculate_data(datatype)
def calculate_data(datatype, force:)
data = {}
auths = authority_list_class.authorities_list
data[ALL_AUTH] = data_for_authority(datatype: datatype)
auths.each { |auth_name| data[auth_name] = data_for_authority(authority_name: auth_name, datatype: datatype) }
data[ALL_AUTH] = data_for_authority(datatype: datatype, force: force)
auths.each { |auth_name| data[auth_name] = data_for_authority(authority_name: auth_name, datatype: datatype, force: force) }
data
end

def data_for_authority(authority_name: nil, datatype:)
def data_for_authority(authority_name: nil, datatype:, force:)
action_data = {}
[:search, :fetch, :all_actions].each do |action|
data = {}
data[FOR_DATATABLE] = data_table_stats(authority_name, action) if calculate_datatable?(datatype)
data[FOR_DATATABLE] = data_table_stats(authority_name, action, force: force) if calculate_datatable?(datatype)
if calculate_graphdata?(datatype)
data[FOR_DAY] = graph_data_service_class.average_last_24_hours(authority_name: authority_name, action: action)
data[FOR_MONTH] = graph_data_service_class.average_last_30_days(authority_name: authority_name, action: action)
data[FOR_YEAR] = graph_data_service_class.average_last_12_months(authority_name: authority_name, action: action)
data[FOR_DAY] = graph_data_service_class.average_last_24_hours(authority_name: authority_name, action: action, force: force)
data[FOR_MONTH] = graph_data_service_class.average_last_30_days(authority_name: authority_name, action: action, force: force)
data[FOR_YEAR] = graph_data_service_class.average_last_12_months(authority_name: authority_name, action: action, force: force)
end
action_data[action] = data
end
action_data
end

# Get statistics for all available data.
# @param [String] auth_name - limit statistics to records for the given authority (default: all authorities)
# @param auth_name [String] limit statistics to records for the given authority (default: all authorities)
# @param action [Symbol] one of :search, :fetch, :all_actions
# @param force [Boolean] if true, forces cache to regenerate; otherwise, returns value from cache unless expired
# @returns [Hash] performance statistics for the datatable during the expected time period
# @example
# { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5,
# retrieve_10th_ms: 12.3, graph_load_10th_ms: 12.3, normalization_10th_ms: 4.2, full_request_10th_ms: 16.5,
# retrieve_90th_ms: 12.3, graph_load_90th_ms: 12.3, normalization_90th_ms: 4.2, full_request_90th_ms: 16.5 }
def data_table_stats(auth_name, action)
records = records_for_last_24_hours(auth_name) ||
records_for_last_30_days(auth_name) ||
records_for_last_12_months(auth_name) ||
all_records(auth_name)
stats_calculator_class.new(records, action: action).calculate_stats(avg: true, low: true, high: true)
def data_table_stats(auth_name, action, force:)
Rails.cache.fetch("#{self.class}/#{__method__}/#{auth_name || ALL_AUTH}/#{action}/#{FOR_DATATABLE}", expires_in: QaServer.cache_expiry, race_condition_ttl: 1.hour, force: force) do
Rails.logger.info("#{self.class}##{__method__} - calculating performance datatable stats - cache expired or refresh requested (#{force})")
records = records_for_last_24_hours(auth_name) ||
records_for_last_30_days(auth_name) ||
records_for_last_12_months(auth_name) ||
all_records(auth_name)
stats_calculator_class.new(records, action: action).calculate_stats(avg: true, low: true, high: true)
end
end

def expected_time_period
Expand Down
92 changes: 56 additions & 36 deletions app/models/qa_server/scenario_run_history.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Provide access to the scenario_results_history database table which tracks specific scenario runs over time.
module QaServer
class ScenarioRunHistory < ActiveRecord::Base
class ScenarioRunHistory < ActiveRecord::Base # rubocop:disable Metrics/ClassLength
self.table_name = 'scenario_run_history'
belongs_to :scenario_run_registry
enum scenario_type: [:connection, :accuracy, :performance], _suffix: :type
Expand Down Expand Up @@ -31,24 +31,28 @@ def self.save_result(run_id:, scenario_result:)
end

# Get a summary of passing/failing tests for a run.
# @param scenario_run [ScenarioRunRegistry] the run on which to gather statistics
# @returns [Hash] statistics on the requested run
# @example
# { run_id: 14,
# failing_count: 3,
# passing_count: 156,
# total_count: 159,
# authority_count: 22,
# failing_authority_count: 1 }
def self.run_summary(scenario_run:)
return nil unless scenario_run&.id
status = status_counts_in_run(run_id: scenario_run.id)
summary_class.new(run_id: scenario_run.id,
run_dt_stamp: scenario_run.dt_stamp,
authority_count: authorities_in_run(run_id: scenario_run.id).count,
failing_authority_count: authorities_with_failures_in_run(run_id: scenario_run.id).count,
passing_scenario_count: status['good'],
failing_scenario_count: status['bad'] + status['unknown'])
# @param scenario_run [QaServer::ScenarioRunRegistry] the run on which to gather statistics
# @param force [Boolean] if true, forces cache to regenerate; otherwise, returns value from cache unless expired
# @returns [QaServer::ScenarioRunSummary] statistics on the requested run
# @example ScenarioRunSummary includes methods for accessing
# * run_id: 14,
# * run_dt_stamp:
# * authority_count: 22,
# * failing_authority_count: 1
# * passing_scenario_count: 156,
# * failing_scenario_count: 3,
# * total_scenario_count: 159,
def self.run_summary(scenario_run:, force: false)
Rails.cache.fetch("#{self.class}/#{__method__}", expires_in: QaServer.cache_expiry, race_condition_ttl: 1.hour, force: force) do
Rails.logger.info("#{self.class}##{__method__} - creating summary of latest run - cache expired or refresh requested (#{force})")
status = status_counts_in_run(run_id: scenario_run.id)
summary_class.new(run_id: scenario_run.id,
run_dt_stamp: scenario_run.dt_stamp,
authority_count: authorities_in_run(run_id: scenario_run.id).count,
failing_authority_count: authorities_with_failures_in_run(run_id: scenario_run.id).count,
passing_scenario_count: status['good'],
failing_scenario_count: status['bad'] + status['unknown'])
end
end

# Get set of all scenario results for a run.
Expand Down Expand Up @@ -85,6 +89,7 @@ def self.run_summary(scenario_run:)
# err_message: "Not enough search results returned",
# scenario_type: :connection
# run_time: 0.123 } ]
# @deprecated Not used anywhere. Being removed.
def self.run_results(run_id:, authority_name: nil, status: nil, url: nil)
return [] unless run_id
where = {}
Expand All @@ -94,9 +99,11 @@ def self.run_results(run_id:, authority_name: nil, status: nil, url: nil)
where[:url] = url if url.present?
QaServer::ScenarioRunHistory.where(where).to_a
end
deprecation_deprecate run_results: "Not used anywhere. Being removed."

# Get set of failures for a run, if any.
# @param run_id [Integer] the run on which to gather statistics
# @param force [Boolean] if true, forces cache to regenerate; otherwise, returns value from cache unless expired
# @returns [Array<Hash>] scenario details for any failing scenarios in the run
# @example
# [ { status: :bad,
Expand All @@ -117,32 +124,43 @@ def self.run_results(run_id:, authority_name: nil, status: nil, url: nil)
# err_message: "Not enough search results returned",
# scenario_type: :connection
# run_time: 0.123 } ]
def self.run_failures(run_id:)
def self.run_failures(run_id:, force: false)
return [] unless run_id
QaServer::ScenarioRunHistory.where(scenario_run_registry_id: run_id).where.not(status: :good).to_a
Rails.cache.fetch("#{self.class}/#{__method__}", expires_in: QaServer.cache_expiry, race_condition_ttl: 1.hour, force: force) do
Rails.logger.info("#{self.class}##{__method__} - finding failures in latest run - cache expired or refresh requested (#{force})")
QaServer::ScenarioRunHistory.where(scenario_run_registry_id: run_id).where.not(status: :good).to_a
end
end

# Get a summary level of historical data
# @returns [Array<Hash>] scenario details for any failing scenarios in the run (auth_name, failing, passing)
# @example
# @returns [Array<Array>] summary of passing/failing tests for each authority
# @example [auth_name, failing, passing]
# [ [ 'agrovoc', 0, 24 ],
# [ 'geonames_ld4l_cache', 2, 22 ] ... ]
def self.historical_summary
runs = all_runs_per_authority
failures = failing_runs_per_authority
return [] unless runs.present?
data = []
runs.each do |auth_name, run_count|
auth_data = []
auth_data[0] = auth_name
failure_count = (failures.key? auth_name) ? failures[auth_name] : 0 # rubocop:disable Style/TernaryParentheses
auth_data[1] = failure_count
auth_data[2] = run_count - failure_count # passing
data << auth_data
def self.historical_summary(force: false)
Rails.cache.fetch("#{self.class}/#{__method__}", expires_in: QaServer.cache_expiry, race_condition_ttl: 1.hour, force: force) do
Rails.logger.info("#{self.class}##{__method__} - calculating pass/fail per authority across all time - cache expired or refresh requested (#{force})")
runs = all_runs_per_authority
failures = failing_runs_per_authority
return [] unless runs.present?
data = []
runs.each do |auth_name, run_count|
data << pass_fail_stats_for_authority(failures, auth_name, run_count)
end
data
end
data
end

def self.pass_fail_stats_for_authority(failures, auth_name, run_count)
auth_data = []
auth_data[0] = auth_name
failure_count = failures.key?(auth_name) ? failures[auth_name] : 0
auth_data[1] = failure_count
auth_data[2] = run_count - failure_count # passing
auth_data
end
private_class_method :pass_fail_stats_for_authority

def self.authorities_in_run(run_id:)
QaServer::ScenarioRunHistory.where(scenario_run_registry_id: run_id).pluck(:authority_name).uniq
end
Expand All @@ -153,6 +171,8 @@ def self.authorities_with_failures_in_run(run_id:)
end
private_class_method :authorities_with_failures_in_run

# @return [Hash] status counts across all authorities (used for current test summary)
# @example { "good" => 23, "bad" => 3, "unknown" => 0 }
def self.status_counts_in_run(run_id:)
status = QaServer::ScenarioRunHistory.group('status').where(scenario_run_registry_id: run_id).count
status["good"] = 0 unless status.key? "good"
Expand Down

0 comments on commit 7dd3cf0

Please sign in to comment.