Skip to content

Commit

Permalink
Merge pull request #448 from LD4P/analytics/historical
Browse files Browse the repository at this point in the history
Add chart showing simulated graph (in table) of the last 30 days of up-down connection data
  • Loading branch information
elrayle authored Apr 17, 2021
2 parents 471eee5 + a365669 commit f41241f
Show file tree
Hide file tree
Showing 14 changed files with 409 additions and 4 deletions.
45 changes: 45 additions & 0 deletions app/assets/stylesheets/qa_server/_monitor-status.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,48 @@ div#performance-by-the-day {
div#performance-by-the-month {
display: none;
}

table.up-down-history {
border: none;
}

td.up-down-history {
font-size: .8em;
font-style: italic;
border: none;
}
th.up-down-history {
width: 20px;
border: none;
}

td.connection-up-down {
border-right: 8px white solid;
border-left: 8px white solid;
border-top: none;
border-bottom: 3px white solid;
}

td.connection-no-data {
background-color: white;
}

td.connection-fully-up {
background-color: #19AE19;
}

td.connection-mostly-up {
background-color: #19AEA7;
}

td.connection-timeouts {
background-color: #EDF908;
}

td.connection-barely-up {
background-color: #AE7619;
}

td.connection-down {
background-color: #CE0303;
}
1 change: 1 addition & 0 deletions app/cache_processors/concerns/qa_server/cache_keys.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module CacheKeys
SCENARIO_RUN_SUMMARY_DATA_CACHE_KEY = "QaServer--CacheKeys--scenario_run_summary_data"
SCENARIO_RUN_FAILURE_DATA_CACHE_KEY = "QaServer--CacheKeys--scenario_run_failure_data"
SCENARIO_RUN_HISTORY_DATA_CACHE_KEY = "QaServer--CacheKeys--scenario_run_history_data"
SCENARIO_RUN_HISTORY_UP_DOWN_DATA_CACHE_KEY = "QaServer--CacheKeys--history_up_down_data"

PERFORMANCE_DATATABLE_DATA_CACHE_KEY = "QaServer--Cache--performance_datatable_data"
end
Expand Down
20 changes: 19 additions & 1 deletion app/cache_processors/qa_server/scenario_history_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
# Maintain a cache of data for Authority Connection History table displayed on Monitor Status page
module QaServer
class ScenarioHistoryCache
class_attribute :scenario_history_class
class_attribute :scenario_history_class, :scenario_up_down_class
self.scenario_history_class = QaServer::ScenarioRunHistory
self.scenario_up_down_class = QaServer::HistoryUpDownService

class << self
include QaServer::CacheKeys
Expand All @@ -21,12 +22,29 @@ def historical_summary(force: false)
end
end

# Get a status for each of the last 30 days for queries that succeeded or failed.
# @param force [Boolean] if true, run the tests even if the cache hasn't expired; otherwise, use cache if not expired
# @returns [Hash<Array>] status for the last 30 days for each authority
# @example { auth_name => [:fully_up, :fully_up, :down, :mostly_up, ... ], ... }
# { 'agrovoc' => [ :fully_up, :fully_up, :down, :mostly_up, ...],
# 'geonames_ld4l_cache' => [ :fully_up, :mostly_up, :down, :fully_up, :timeouts, ...] }
def historical_up_down_data(force: false)
Rails.cache.fetch(cache_key_for_historical_up_down_data, expires_in: next_expiry, race_condition_ttl: 30.seconds, force: force) do
QaServer.config.monitor_logger.debug("(QaServer::ScenarioHistoryCache) - CALCULATING UP-DOWN STATUS HISTORY of scenario runs (force: #{force})")
scenario_up_down_class.new.last_30_days
end
end

private

def cache_key_for_historical_data
SCENARIO_RUN_HISTORY_DATA_CACHE_KEY
end

def cache_key_for_historical_up_down_data
SCENARIO_RUN_HISTORY_UP_DOWN_DATA_CACHE_KEY
end

def next_expiry
QaServer::CacheExpiryService.cache_expiry
end
Expand Down
8 changes: 8 additions & 0 deletions app/controllers/qa_server/monitor_status_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def index
@presenter = presenter_class.new(current_summary: latest_summary,
current_failure_data: latest_failures,
historical_summary_data: historical_data,
historical_up_down_data: historical_up_down_data,
performance_data: performance_table_data)
QaServer.config.monitor_logger.debug("~~~~~~~~ DONE rendering monitor status")
render 'index', status: :internal_server_error if latest_summary&.failing_authority_count&.positive?
Expand Down Expand Up @@ -59,6 +60,13 @@ def historical_data
@historical_data ||= QaServer::ScenarioHistoryCache.historical_summary(force: refresh_history?)
end

# Get a summary level of historical data
# @returns [Array<Hash>] summary of passing/failing tests for each authority
# @see QaServer::ScenarioRunHistory#historical_summary for structure of output
def historical_up_down_data
@historical_up_down_data ||= QaServer::ScenarioHistoryCache.historical_up_down_data(force: refresh_history?)
end

def update_historical_graph
return unless QaServer.config.display_historical_graph?
QaServer::ScenarioHistoryGraphCache.generate_graph(data: historical_data, force: refresh_history?)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true
# This presenter class provides historical testing data needed by the view that monitors status of authorities.
module QaServer::MonitorStatus
class HistoryUpDownPresenter
attr_reader :historical_up_down_data

# @param parent [QaServer::MonitorStatusPresenter] parent presenter
# @param historical_up_down_data [Hash<Array>] recent connection status of queries (typically last 30 days)
# @example historical_up_down_data
# { 'AGROVOC' = [
# :FULLY_UP, # 0 - today
# :MOSTLY_UP, # 1 - yesterday
# :MOSTLY_UP, # 2 - two days ago
# :FULLY_UP, # 3 - three days ago
# :DOWN, # 4 - four days ago
# ... # etc.
# ],
# 'CERL' = [ ... ]
# }
def initialize(parent:, historical_up_down_data:)
@parent = parent
@historical_up_down_data = historical_up_down_data
end

# Return the last date of data represented in the history graph and data table
# @return [ActiveSupport::TimeWithZone] date time stamp
def up_down_start
QaServer::TimeService.pretty_date(up_down_end_dt - 29.days)
end

def up_down_end
QaServer::TimeService.pretty_date(up_down_end_dt)
end

def up_down_end_dt
@parent.last_updated_dt
end

# @param status [Symbol] :fully_up, :mostly_up, :timeouts, :barely_up, :down
# @param day [Integer] retrieve the status for this day
# @return [String] name of the css class for the status
def historical_up_down_status_class(status, day) # rubocop:disable Metrics/CyclomaticComplexity
case status[day]
when :no_date then 'connection-no-date'
when :fully_up then 'connection-fully-up'
when :mostly_up then 'connection-mostly-up'
when :timeouts then 'connection-timeouts'
when :barely_up then 'connection-barely-up'
when :down then 'connection-down'
end
end

# @return [Boolean] true if historical datatable should be visible; otherwise false
def display_historical_up_down?
QaServer.config.display_historical_datatable? && @historical_up_down_data.present?
end
end
end
9 changes: 7 additions & 2 deletions app/presenters/qa_server/monitor_status_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ class MonitorStatusPresenter
extend Forwardable

# @param current_summary [ScenarioRunSummary] summary status of the latest run of test scenarios
# @param current_data [Array<Hash>] current set of failures for the latest test run, if any
# @param current_failure_data [Array<Hash>] current set of failures for the latest test run, if any
# @param historical_summary_data [Array<Hash>] summary of past failuring runs per authority to drive chart
# @param historical_up_down_data [Hash<Array>] status of queries for the last 30 days
# @param performance_data [Hash<Hash>] performance datatable data
def initialize(current_summary:, current_failure_data:, historical_summary_data:, performance_data:)
def initialize(current_summary:, current_failure_data:, historical_summary_data:, historical_up_down_data:, performance_data:)
@current_status_presenter = QaServer::MonitorStatus::CurrentStatusPresenter.new(parent: self, current_summary: current_summary, current_failure_data: current_failure_data)
@history_presenter = QaServer::MonitorStatus::HistoryPresenter.new(parent: self, historical_summary_data: historical_summary_data)
@history_up_down_presenter = QaServer::MonitorStatus::HistoryUpDownPresenter.new(parent: self, historical_up_down_data: historical_up_down_data)
@performance_presenter = QaServer::MonitorStatus::PerformancePresenter.new(parent: self, performance_data: performance_data)
end

Expand All @@ -23,6 +25,9 @@ def initialize(current_summary:, current_failure_data:, historical_summary_data:
:percent_authority_failing, :percent_authority_failing_str, :failure_style_class, :passing_style_class,
:display_history_details?, :display_historical_graph?, :display_historical_datatable?, :history_start, :history_end

def_delegators :@history_up_down_presenter, :historical_up_down_data, :display_historical_up_down?, :historical_up_down_status_class,
:up_down_start, :up_down_end

def_delegators :@performance_presenter, :performance_data, :performance_data?, :display_performance?, :display_performance_graph?,
:display_performance_datatable?, :performance_data_authority_name, :performance_for_day_graph, :performance_for_month_graph,
:performance_for_year_graph, :datatable_search_stats, :datatable_fetch_stats, :datatable_all_actions_stats,
Expand Down
103 changes: 103 additions & 0 deletions app/services/qa_server/history_up_down_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# frozen_string_literal: true
# This class determines the state (e.g. fully_up, mostly_up, barely_up, down)of an authority during the last 30 days.
module QaServer
class HistoryUpDownService
NO_DATA = :no_data
FULLY_UP = :fully_up
MOSTLY_UP = :mostly_up
EXCESSIVE_TIMEOUTS = :timeouts
BARELY_UP = :barely_up
DOWN = :down

MOSTLY_UP_THRESHOLD = QaServer.config.up_down_data_mostly_up_threshold
TIMEOUT_THRESHOLD = QaServer.config.up_down_data_timeouts_max_threshold

class_attribute :authority_lister, :scenario_history_class, :time_service
self.authority_lister = QaServer::AuthorityListerService
self.scenario_history_class = QaServer::ScenarioRunHistory
self.time_service = QaServer::TimeService

def last_30_days
data = {}
authorities_list.each { |authority| data[authority] = last_30_days_for(authority.to_s) }
data
end

private

# @returns [Hash <Array<Hash>>] data for an authority for each of the last 30 days
# @example
# { 'AGROVOC' = [
# :FULLY_UP, # 0 - today
# :MOSTLY_UP, # 1 - yesterday
# :MOSTLY_UP, # 2 - two days ago
# :FULLY_UP, # 3 - three days ago
# :DOWN, # 4 - four days ago
# ... # etc.
# ]
# }
def last_30_days_for(authority)
auth_data = []
0.upto(29) { |offset| auth_data[offset] = day_status(authority, offset) }
auth_data
end

# @returns [Symbol] status for a given day for an authority
def day_status(authority, offset)
day = offset_day(offset)
good_count = count_good(authority, day)
unknown_count = count_unknown(authority, day)
bad_count = count_bad(authority, day)
timeout_count = count_timeouts(authority, day)
status_determination(good_count, unknown_count, bad_count, timeout_count)
end

def status_determination(good_count, unknown_count, bad_count, timeout_count) # rubocop:disable Metrics/CyclomaticComplexity
total_count = good_count + unknown_count + bad_count
return NO_DATA if total_count.zero?
return FULLY_UP if good_count == total_count
return DOWN if bad_count == total_count
return BARELY_UP if unknown_count == total_count
return EXCESSIVE_TIMEOUTS if (timeout_count.to_f / total_count) > TIMEOUT_THRESHOLD
return MOSTLY_UP if (bad_count.to_f / total_count) < (1 - MOSTLY_UP_THRESHOLD)
BARELY_UP
end

def authorities_list
@authorities_list ||= authority_lister.authorities_list
end

def offset_day(offset)
@today ||= time_service.current_time
time_service.pretty_query_date(@today - offset.days)
end

def count_good(authority, day)
scenario_history_class.where(authority_name: authority)
.where(date: day)
.where(status: :good)
.count(:id)
end

def count_unknown(authority, day)
scenario_history_class.where(authority_name: authority)
.where(date: day)
.where(status: :unknown)
.count(:id)
end

def count_bad(authority, day)
scenario_history_class.where(authority_name: authority)
.where(date: day)
.where(status: :bad)
.count(:id)
end

def count_timeouts(authority, day)
scenario_history_class.where(authority_name: authority)
.where(date: day)
.where('err_message LIKE ?', "%timeout%")
.count(:id)
end
end
end
6 changes: 6 additions & 0 deletions app/services/qa_server/time_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ def pretty_time(dt)
def pretty_date(dt)
dt.in_time_zone(QaServer.config.preferred_time_zone_name).strftime("%m/%d/%Y")
end

# @param dt [ActiveSupport::TimeWithZone] date time stamp
# @return [String] string version of date formatted with just date (e.g. "2020-02-01")
def pretty_query_date(dt)
dt.in_time_zone(QaServer.config.preferred_time_zone_name).strftime("%Y-%m-%d")
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<% if @presenter.history? && @presenter.display_history_details?%>
<div id="availability-history" class="status-section">
<h3><%= t('qa_server.monitor_status.history.title') %></h3>
<% if @presenter.display_historical_graph? %>
<p class="status-update-dtstamp"><%= t('qa_server.monitor_status.history.range', from: @presenter.history_start, to: @presenter.history_end) %></p>
<%= image_tag(@presenter.historical_graph, alt: 'History Graph Unavailable') %>
<% end %>

<% if @presenter.display_historical_up_down? %>
<p class="status-update-dtstamp"><%= t('qa_server.monitor_status.history.range', from: @presenter.up_down_start, to: @presenter.up_down_end) %></p>
<table class="up-down-history">
<tr>
<th class="up-down-history"><%= t('qa_server.monitor_status.history.authority') %></th>
<% 0.upto(29) do %>
<th class='up-down-history'></th>
<% end %>
<td class='up-down-history'>Most Recent</td>
</tr>
<% @presenter.historical_up_down_data.each do |authority_name, status| %>
<tr>
<td class="connection-up-down"><%= authority_name %></td>
<% 29.downto(0) do |day| %>
<td class="connection-up-down <%= @presenter.historical_up_down_status_class(status, day) %>"></td>
<% end %>
</tr>
<% end %>
</table>
<% end %>
</div>
<% end %>
3 changes: 2 additions & 1 deletion app/views/qa_server/monitor_status/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
<h2><%= t('qa_server.monitor_status.title') %></h2>

<%= render 'test_summary' %>
<%= render 'test_history' %>
<%= render 'test_up_down_connection_history' %>
<% # = render 'test_history' %>
<%= render 'performance' %>

</div>
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
# @param [Symbol] time period for calculating historical pass/fail (i.e., one of :month, :year, or :all)
# config.historical_datatable_default_time_period = :year

# Threshold for percentage of queries that timed out after which it gets marked in the Authority Connection up-down History
# @param [Float] percentage of queries that are ok to timeout
# config.up_down_data_timeouts_max_threshold = 0.3

# Threshold for percentage of queries that are passing, below which are marked in the Authority Connection up-down History as barely_up
# @param [Float] required percentage of queries passing to be considered mostly-up when there are some failures
# config.up_down_data_mostly_up_threshold = 0.95

# Displays a graph of performance test data when true
# @param [Boolean] display performance graph when true
# config.display_performance_graph = false
Expand Down
14 changes: 14 additions & 0 deletions lib/qa_server/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@ def historical_datatable_default_time_period
@historical_datatable_default_time_period ||= :year
end

# Threshold for percentage of queries that timed out after which it gets marked in the Authority Connection up-down History
# @param [Float] percentage of queries that are ok to timeout
attr_writer :up_down_data_timeouts_max_threshold
def up_down_data_timeouts_max_threshold
@up_down_data_timeouts_max_threshold ||= 0.3
end

# Threshold for percentage of queries that are passing, below which are marked in the Authority Connection up-down History as barely_up
# @param [Float] required percentage of queries passing to be considered mostly-up when there are some failures
attr_writer :up_down_data_mostly_up_threshold
def up_down_data_mostly_up_threshold
@up_down_data_mostly_up_threshold ||= 0.95
end

# Displays a graph of performance test data when true
# @param [Boolean] display performance graph when true
attr_writer :display_performance_graph
Expand Down
Loading

0 comments on commit f41241f

Please sign in to comment.