qa_server 5.5.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +53 -0
  4. data/app/controllers/{qa_server/authority_validation_controller.rb → concerns/qa_server/authority_validation_behavior.rb} +13 -11
  5. data/app/controllers/qa_server/authority_list_controller.rb +5 -1
  6. data/app/controllers/qa_server/check_status_controller.rb +5 -1
  7. data/app/controllers/qa_server/monitor_status_controller.rb +40 -28
  8. data/app/jobs/qa_server/monitor_tests_job.rb +50 -0
  9. data/app/models/qa_server/performance_cache.rb +11 -3
  10. data/app/models/qa_server/performance_history.rb +24 -106
  11. data/app/models/qa_server/scenario_run_history.rb +161 -176
  12. data/app/models/qa_server/scenario_run_registry.rb +4 -4
  13. data/app/prepends/prepended_linked_data/find_term.rb +4 -4
  14. data/app/prepends/prepended_linked_data/search_query.rb +4 -4
  15. data/app/prepends/prepended_rdf/rdf_graph.rb +4 -4
  16. data/app/presenters/concerns/qa_server/monitor_status/performance_datatable_behavior.rb +23 -1
  17. data/app/presenters/qa_server/check_status_presenter.rb +4 -4
  18. data/app/presenters/qa_server/monitor_status/current_status_presenter.rb +17 -5
  19. data/app/presenters/qa_server/monitor_status/history_presenter.rb +40 -19
  20. data/app/presenters/qa_server/monitor_status/performance_presenter.rb +3 -1
  21. data/app/presenters/qa_server/monitor_status_presenter.rb +9 -9
  22. data/app/services/qa_server/monitor_cache_service.rb +22 -0
  23. data/app/services/qa_server/performance_calculator_service.rb +18 -7
  24. data/app/services/qa_server/performance_datatable_service.rb +82 -0
  25. data/app/services/qa_server/performance_graph_data_service.rb +140 -82
  26. data/app/services/qa_server/performance_graphing_service.rb +15 -12
  27. data/app/services/qa_server/time_period_service.rb +93 -0
  28. data/app/services/qa_server/time_service.rb +29 -0
  29. data/app/validators/qa_server/scenario_validator.rb +3 -3
  30. data/app/validators/qa_server/search_scenario_validator.rb +3 -3
  31. data/app/views/qa_server/monitor_status/_performance.html.erb +2 -1
  32. data/app/views/qa_server/monitor_status/_test_history.html.erb +1 -2
  33. data/app/views/qa_server/monitor_status/_test_summary.html.erb +2 -2
  34. data/config/locales/qa_server.en.yml +3 -4
  35. data/lib/generators/qa_server/templates/config/initializers/qa_server.rb +4 -0
  36. data/lib/qa_server.rb +0 -23
  37. data/lib/qa_server/configuration.rb +20 -0
  38. data/lib/qa_server/version.rb +1 -1
  39. data/spec/lib/qa_server_spec.rb +0 -51
  40. data/spec/services/qa_server/monitor_cache_service_spec.rb +20 -0
  41. data/spec/services/qa_server/time_period_service_spec.rb +246 -0
  42. data/spec/services/qa_server/time_service_spec.rb +50 -0
  43. metadata +14 -3
@@ -17,15 +17,15 @@ module PrependedRdf::RdfGraph
17
17
  raise TypeError, "#{self} is immutable" if immutable?
18
18
  phid, real_url = parse_phid(url)
19
19
  performance_udpates = {}
20
- start_time_s = QaServer.current_time_s
20
+ start_time_s = QaServer::TimeService.current_time_s
21
21
 
22
22
  reader = RDF::Reader.open(real_url, { base_uri: real_url }.merge(options))
23
23
 
24
- end_time_s = QaServer.current_time_s
24
+ end_time_s = QaServer::TimeService.current_time_s
25
25
  performance_udpates[:retrieve_time_ms] = (end_time_s - start_time_s) * 1000
26
26
  QaServer.config.performance_tracker.write "#{format('%.6f', end_time_s - start_time_s)}, " # read data
27
27
 
28
- start_time_s = QaServer.current_time_s
28
+ start_time_s = QaServer::TimeService.current_time_s
29
29
 
30
30
  if graph_name
31
31
  statements = []
@@ -40,7 +40,7 @@ module PrependedRdf::RdfGraph
40
40
  nil
41
41
  end
42
42
 
43
- end_time_s = QaServer.current_time_s
43
+ end_time_s = QaServer::TimeService.current_time_s
44
44
  performance_udpates[:graph_load_time_ms] = (end_time_s - start_time_s) * 1000
45
45
  QaServer.config.performance_cache.update(id: phid, updates: performance_udpates)
46
46
  QaServer.config.performance_tracker.write "#{format('%.6f', end_time_s - start_time_s)}, " # load graph
@@ -94,6 +94,28 @@ module QaServer::MonitorStatus
94
94
  end
95
95
  end
96
96
 
97
+ def performance_data_start
98
+ start_dt = case expected_time_period
99
+ when :day
100
+ performance_data_end_dt - 1.day
101
+ when :month
102
+ performance_data_end_dt - 1.month
103
+ when :year
104
+ performance_data_end_dt - 1.year
105
+ else
106
+ @parent.first_updated_dt
107
+ end
108
+ QaServer::TimeService.pretty_date(start_dt)
109
+ end
110
+
111
+ def performance_data_end_dt
112
+ @parent.last_updated_dt
113
+ end
114
+
115
+ def performance_data_end
116
+ QaServer::TimeService.pretty_date(performance_data_end_dt)
117
+ end
118
+
97
119
  private
98
120
 
99
121
  def expected_time_period
@@ -101,7 +123,7 @@ module QaServer::MonitorStatus
101
123
  end
102
124
 
103
125
  def data_table_for(authority_data, action)
104
- authority_data[action][FOR_DATATABLE]
126
+ authority_data[action]
105
127
  end
106
128
 
107
129
  def unsupported_action?(stats)
@@ -76,11 +76,11 @@ module QaServer
76
76
  end
77
77
 
78
78
  def value_check_param
79
- QaServer::AuthorityValidationController::VALIDATION_TYPE_PARAM
79
+ QaServer::AuthorityValidationBehavior::VALIDATION_TYPE_PARAM
80
80
  end
81
81
 
82
82
  def value_check_connections
83
- QaServer::AuthorityValidationController::VALIDATE_CONNECTIONS
83
+ QaServer::AuthorityValidationBehavior::VALIDATE_CONNECTIONS
84
84
  end
85
85
 
86
86
  def label_check_connections
@@ -88,7 +88,7 @@ module QaServer
88
88
  end
89
89
 
90
90
  def value_check_accuracy
91
- QaServer::AuthorityValidationController::VALIDATE_ACCURACY
91
+ QaServer::AuthorityValidationBehavior::VALIDATE_ACCURACY
92
92
  end
93
93
 
94
94
  def label_check_accuracy
@@ -96,7 +96,7 @@ module QaServer
96
96
  end
97
97
 
98
98
  def value_all_checks
99
- QaServer::AuthorityValidationController::ALL_VALIDATIONS
99
+ QaServer::AuthorityValidationBehavior::ALL_VALIDATIONS
100
100
  end
101
101
 
102
102
  def label_all_checks
@@ -2,23 +2,35 @@
2
2
  # This presenter class provides data related to last test run as needed by the view that monitors status of authorities.
3
3
  module QaServer::MonitorStatus
4
4
  class CurrentStatusPresenter
5
+ # @param parent [QaServer::MonitorStatusPresenter] parent presenter
5
6
  # @param current_summary [ScenarioRunSummary] summary status of the latest run of test scenarios
6
7
  # @param current_data [Array<Hash>] current set of failures for the latest test run, if any
7
- def initialize(current_summary:, current_failure_data:)
8
+ def initialize(parent:, current_summary:, current_failure_data:)
9
+ @parent = parent
8
10
  @current_summary = current_summary
9
11
  @current_failure_data = current_failure_data
10
12
  end
11
13
 
12
- # @return [String] date of last test run
14
+ # @return [ActiveSupport::TimeWithZone] date time stamp of last test run
15
+ def last_updated_dt
16
+ @current_summary.run_dt_stamp
17
+ end
18
+
19
+ # @return [String] date with time of last test run
13
20
  def last_updated
14
- @current_summary.run_dt_stamp.in_time_zone("Eastern Time (US & Canada)").strftime("%m/%d/%y - %I:%M %p")
21
+ QaServer::TimeService.pretty_time(last_updated_dt)
15
22
  end
16
23
 
17
- # @return [String] date of first recorded test run
18
- def first_updated
24
+ # @return [ActiveSupport::TimeWithZone] date time stamp of first recorded test run
25
+ def first_updated_dt
19
26
  QaServer::ScenarioRunRegistry.first_run_dt
20
27
  end
21
28
 
29
+ # @return [String] date with time of first recorded test run
30
+ def first_updated
31
+ QaServer::TimeService.pretty_time(first_updated_dt)
32
+ end
33
+
22
34
  # @return [Integer] number of loaded authorities
23
35
  def authorities_count
24
36
  @current_summary.authority_count
@@ -1,15 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
  # This presenter class provides historical testing data needed by the view that monitors status of authorities.
3
3
  module QaServer::MonitorStatus
4
- class HistoryPresenter
5
- HISTORICAL_AUTHORITY_NAME_IDX = 0
6
- HISTORICAL_FAILURE_COUNT_IDX = 1
7
- HISTORICAL_PASSING_COUNT_IDX = 2
8
-
4
+ class HistoryPresenter # rubocop:disable Metrics/ClassLength
9
5
  include QaServer::MonitorStatus::GruffGraph
10
6
 
7
+ # @param parent [QaServer::MonitorStatusPresenter] parent presenter
11
8
  # @param historical_summary_data [Array<Hash>] summary of past failuring runs per authority to drive chart
12
- def initialize(historical_summary_data:)
9
+ def initialize(parent:, historical_summary_data:)
10
+ @parent = parent
13
11
  @historical_summary_data = historical_summary_data
14
12
  end
15
13
 
@@ -23,8 +21,33 @@ module QaServer::MonitorStatus
23
21
 
24
22
  # @return [Boolean] true if historical test data exists; otherwise false
25
23
  def history?
26
- return true if @historical_summary_data.present?
27
- false
24
+ @historical_summary_data.present?
25
+ end
26
+
27
+ # Return the first date of data represented in the history graph and data table
28
+ # @return [String] string version of date formatted with just date (e.g. "02/01/2020")
29
+ def history_start
30
+ start_dt = case QaServer.config.historical_datatable_default_time_period
31
+ when :month
32
+ history_end_dt - 1.month
33
+ when :year
34
+ history_end_dt - 1.year
35
+ else
36
+ @parent.first_updated_dt
37
+ end
38
+ QaServer::TimeService.pretty_date(start_dt)
39
+ end
40
+
41
+ # Return the last date of data represented in the history graph and data table
42
+ # @return [ActiveSupport::TimeWithZone] date time stamp
43
+ def history_end_dt
44
+ @parent.last_updated_dt
45
+ end
46
+
47
+ # Return the last date of data represented in the history graph and data table
48
+ # @return [String] string version of date formatted with just date (e.g. "02/01/2020")
49
+ def history_end
50
+ QaServer::TimeService.pretty_date(history_end_dt)
28
51
  end
29
52
 
30
53
  def historical_graph
@@ -57,15 +80,15 @@ module QaServer::MonitorStatus
57
80
  end
58
81
 
59
82
  def historical_data_authority_name(historical_entry)
60
- historical_entry[HISTORICAL_AUTHORITY_NAME_IDX]
83
+ historical_entry[0]
61
84
  end
62
85
 
63
86
  def days_authority_passing(historical_entry)
64
- historical_entry[HISTORICAL_PASSING_COUNT_IDX]
87
+ historical_entry[1]["good"]
65
88
  end
66
89
 
67
90
  def days_authority_failing(historical_entry)
68
- historical_entry[HISTORICAL_FAILURE_COUNT_IDX]
91
+ historical_entry[1]["bad"]
69
92
  end
70
93
 
71
94
  def days_authority_tested(historical_entry)
@@ -82,13 +105,11 @@ module QaServer::MonitorStatus
82
105
 
83
106
  def failure_style_class(historical_entry)
84
107
  return "status-neutral" if days_authority_failing(historical_entry) <= 0
85
- return "status-unknown" if percent_authority_failing(historical_entry) < 0.1
86
- "status-bad"
108
+ percent_authority_failing(historical_entry) < 0.1 ? "status-unknown" : "status-bad"
87
109
  end
88
110
 
89
111
  def passing_style_class(historical_entry)
90
- return "status-bad" if days_authority_passing(historical_entry) <= 0
91
- "status-good"
112
+ days_authority_passing(historical_entry) <= 0 ? "status-bad" : "status-good"
92
113
  end
93
114
 
94
115
  def display_history_details?
@@ -125,11 +146,11 @@ module QaServer::MonitorStatus
125
146
  pass_data = []
126
147
  fail_data = []
127
148
  i = 0
128
- historical_summary.each do |data|
129
- labels[i] = data[0]
149
+ historical_summary.each do |auth, data|
150
+ labels[i] = auth
130
151
  i += 1
131
- fail_data << data[1]
132
- pass_data << data[2]
152
+ fail_data << data["bad"]
153
+ pass_data << data["good"]
133
154
  end
134
155
  [labels, fail_data, pass_data]
135
156
  end
@@ -7,8 +7,10 @@ module QaServer::MonitorStatus
7
7
  include QaServer::MonitorStatus::PerformanceGraphBehavior
8
8
  include QaServer::PerformanceHistoryDataKeys
9
9
 
10
+ # @param parent [QaServer::MonitorStatusPresenter] parent presenter
10
11
  # @param performance_data [Hash<Hash>] performance data
11
- def initialize(performance_data:)
12
+ def initialize(parent:, performance_data:)
13
+ @parent = parent
12
14
  @performance_data = performance_data
13
15
  end
14
16
 
@@ -7,21 +7,21 @@ module QaServer
7
7
  # @param current_summary [ScenarioRunSummary] summary status of the latest run of test scenarios
8
8
  # @param current_data [Array<Hash>] current set of failures for the latest test run, if any
9
9
  # @param historical_summary_data [Array<Hash>] summary of past failuring runs per authority to drive chart
10
- # @param performance_data [Hash<Hash>] performance data
10
+ # @param performance_data [Hash<Hash>] performance datatable data
11
11
  def initialize(current_summary:, current_failure_data:, historical_summary_data:, performance_data:)
12
- @current_status_presenter = QaServer::MonitorStatus::CurrentStatusPresenter.new(current_summary: current_summary, current_failure_data: current_failure_data)
13
- @history_presenter = QaServer::MonitorStatus::HistoryPresenter.new(historical_summary_data: historical_summary_data)
14
- @performance_presenter = QaServer::MonitorStatus::PerformancePresenter.new(performance_data: performance_data)
12
+ @current_status_presenter = QaServer::MonitorStatus::CurrentStatusPresenter.new(parent: self, current_summary: current_summary, current_failure_data: current_failure_data)
13
+ @history_presenter = QaServer::MonitorStatus::HistoryPresenter.new(parent: self, historical_summary_data: historical_summary_data)
14
+ @performance_presenter = QaServer::MonitorStatus::PerformancePresenter.new(parent: self, performance_data: performance_data)
15
15
  end
16
16
 
17
- def_delegators :@current_status_presenter, :last_updated, :first_updated, :authorities_count, :failing_authorities_count,
18
- :authorities_count_style, :tests_count, :passing_tests_count, :failing_tests_count, :failing_tests_style,
19
- :failures, :failures?
17
+ def_delegators :@current_status_presenter, :last_updated_dt, :last_updated, :first_updated_dt, :first_updated, :authorities_count,
18
+ :failing_authorities_count, :authorities_count_style, :tests_count, :passing_tests_count, :failing_tests_count,
19
+ :failing_tests_style, :failures, :failures?
20
20
 
21
21
  def_delegators :@history_presenter, :historical_summary, :history?, :historical_graph, :status_style_class, :status_label,
22
22
  :historical_data_authority_name, :days_authority_passing, :days_authority_failing, :days_authority_tested,
23
23
  :percent_authority_failing, :percent_authority_failing_str, :failure_style_class, :passing_style_class,
24
- :display_history_details?, :display_historical_graph?, :display_historical_datatable?
24
+ :display_history_details?, :display_historical_graph?, :display_historical_datatable?, :history_start, :history_end
25
25
 
26
26
  def_delegators :@performance_presenter, :performance_data, :performance_data?, :display_performance?, :display_performance_graph?,
27
27
  :display_performance_datatable?, :performance_data_authority_name, :performance_for_day_graph, :performance_for_month_graph,
@@ -35,6 +35,6 @@ module QaServer
35
35
  :performance_graph_data_section_id, :performance_graph_data_section_base_id, :performance_data_section_class,
36
36
  :performance_day_graph?, :performance_month_graph?, :performance_year_graph?, :performance_all_actions_graph?,
37
37
  :performance_search_graph?, :performance_fetch_graph?, :performance_table_description, :performance_graph_time_period,
38
- :performance_graph_action
38
+ :performance_graph_action, :performance_data_start, :performance_data_end
39
39
  end
40
40
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ # Helper methods for caching for monitoring status.
3
+ module QaServer
4
+ class MonitorCacheService
5
+ class << self
6
+ # @return [Float] number of seconds until cache should expire
7
+ def cache_expiry
8
+ monitoring_expires_at - QaServer::TimeService.current_time
9
+ end
10
+
11
+ private
12
+
13
+ # @return [ActiveSupport::TimeWithZone] DateTime at which cache should expire
14
+ def monitoring_expires_at
15
+ offset = QaServer.config.hour_offset_to_expire_cache
16
+ offset_time = QaServer::TimeService.current_time
17
+ offset_time = offset_time.tomorrow unless (offset_time + 5.minutes).hour < offset
18
+ offset_time.beginning_of_day + offset.hours - 5.minutes
19
+ end
20
+ end
21
+ end
22
+ end
@@ -14,18 +14,29 @@ module QaServer
14
14
  @stats = {}
15
15
  end
16
16
 
17
- # Calculate performance statistics for a set of PerformanceHistory records. Min is at the 10th percentile. Max is at the 90th percentile.
17
+ # Calculate performance statistics with percentiles. Min is at the 10th percentile. Max is at the 90th percentile.
18
18
  # @return [Hash] hash of the statistics
19
19
  # @example
20
20
  # { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5,
21
21
  # retrieve_10th_ms: 12.3, graph_load_10th_ms: 12.3, normalization_10th_ms: 4.2, full_request_10th_ms: 16.5,
22
22
  # retrieve_90th_ms: 12.3, graph_load_90th_ms: 12.3, normalization_90th_ms: 4.2, full_request_90th_ms: 16.5 }
23
- def calculate_stats(avg: false, low: false, high: false, load: true, norm: true, full: true) # rubocop:disable Metrics/ParameterLists
24
- calculate_load_stats(avg, low, high) if load
25
- calculate_retrieve_stats(avg, low, high) if load
26
- calculate_graph_load_stats(avg, low, high) if load
27
- calculate_normalization_stats(avg, low, high) if norm
28
- calculate_action_stats(avg, low, high) if full
23
+ def calculate_stats_with_percentiles
24
+ calculate_retrieve_stats(true, true, true)
25
+ calculate_graph_load_stats(true, true, true)
26
+ calculate_normalization_stats(true, true, true)
27
+ calculate_action_stats(true, true, true)
28
+ stats
29
+ end
30
+
31
+ # Calculate performance statistics including averages only.
32
+ # @return [Hash] hash of the statistics
33
+ # @example
34
+ # { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5 }
35
+ def calculate_average_stats
36
+ calculate_load_stats(true, false, false) # used for backward compatibility only
37
+ calculate_retrieve_stats(true, false, false)
38
+ calculate_graph_load_stats(true, false, false)
39
+ calculate_normalization_stats(true, false, false)
29
40
  stats
30
41
  end
31
42
 
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+ # This class calculates performance stats for the performance datatable.
3
+ module QaServer
4
+ class PerformanceDatatableService
5
+ class << self
6
+ include QaServer::PerformanceHistoryDataKeys
7
+
8
+ class_attribute :stats_calculator_class, :performance_data_class, :authority_list_class
9
+ self.stats_calculator_class = QaServer::PerformanceCalculatorService
10
+ self.performance_data_class = QaServer::PerformanceHistory
11
+ self.authority_list_class = QaServer::AuthorityListerService
12
+
13
+ # Summary of performance by action for each authority for the configured time period (e.g. :day, :month, :year, :all).
14
+ # @param force [Boolean] if true, calculate the stats even if the cache hasn't expired; otherwise, use cache if not expired
15
+ # @returns [Hash] performance statistics for configured time period by action for each authority
16
+ # @example
17
+ # { all_authorities:
18
+ # { search:
19
+ # { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5,
20
+ # retrieve_10th_ms: 12.3, graph_load_10th_ms: 12.3, normalization_10th_ms: 4.2, full_request_10th_ms: 16.5,
21
+ # retrieve_90th_ms: 12.3, graph_load_90th_ms: 12.3, normalization_90th_ms: 4.2, full_request_90th_ms: 16.5 },
22
+ # fetch: { ... # same data as for search_stats },
23
+ # all: { ... # same data as for search_stats }
24
+ # },
25
+ # AGROVOC_LD4L_CACHE: { ... # same data for each authority }
26
+ # }
27
+ def calculate_datatable_data(force:)
28
+ Rails.cache.fetch("QaServer::PerformanceDatatableService/#{__method__}", expires_in: QaServer::MonitorCacheService.cache_expiry, race_condition_ttl: 5.minutes, force: force) do
29
+ QaServer.config.monitor_logger.info("(QaServer::PerformanceDatatableService##{__method__}) - calculating performance datatable stats - cache expired or refresh requested (force: #{force})")
30
+ data = {}
31
+ auths = authority_list_class.authorities_list
32
+ data[ALL_AUTH] = datatable_data_for_authority
33
+ auths.each { |auth_name| data[auth_name] = datatable_data_for_authority(authority_name: auth_name) }
34
+ data
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def datatable_data_for_authority(authority_name: nil)
41
+ [:search, :fetch, :all_actions].each_with_object({}) do |action, hash|
42
+ hash[action] = data_table_stats(authority_name, action)
43
+ end
44
+ end
45
+
46
+ # Get statistics for data table.
47
+ # @param auth_name [String] limit statistics to records for the given authority (default: all authorities)
48
+ # @param action [Symbol] one of :search, :fetch, :all_actions
49
+ # @returns [Hash] performance statistics for the datatable during the expected time period
50
+ # @example
51
+ # { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5,
52
+ # retrieve_10th_ms: 12.3, graph_load_10th_ms: 12.3, normalization_10th_ms: 4.2, full_request_10th_ms: 16.5,
53
+ # retrieve_90th_ms: 12.3, graph_load_90th_ms: 12.3, normalization_90th_ms: 4.2, full_request_90th_ms: 16.5 }
54
+ def data_table_stats(auth_name, action)
55
+ records = records_for_authority(auth_name)
56
+ stats_calculator_class.new(records, action: action).calculate_stats_with_percentiles
57
+ end
58
+
59
+ def expected_time_period
60
+ QaServer.config.performance_datatable_default_time_period
61
+ end
62
+
63
+ def records_for_authority(auth_name)
64
+ case expected_time_period
65
+ when :day
66
+ QaServer.config.performance_cache.write_all # only need to write if just using today's data
67
+ performance_data_class.where(QaServer::TimePeriodService.where_clause_for_last_24_hours(auth_name: auth_name))
68
+ when :month
69
+ performance_data_class.where(QaServer::TimePeriodService.where_clause_for_last_30_days(auth_name: auth_name))
70
+ when :year
71
+ performance_data_class.where(QaServer::TimePeriodService.where_clause_for_last_12_months(auth_name: auth_name))
72
+ else
73
+ all_records(auth_name)
74
+ end
75
+ end
76
+
77
+ def all_records(auth_name)
78
+ auth_name.nil? ? performance_data_class.all : performance_data_class.where(authority: auth_name)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,76 +1,133 @@
1
1
  # frozen_string_literal: true
2
2
  # This class sets performance stats for the last 24 hours, past 30 days, and the past 12 months.
3
3
  module QaServer
4
- class PerformanceGraphDataService
4
+ class PerformanceGraphDataService # rubocop:disable Metrics/ClassLength
5
5
  class << self
6
6
  include QaServer::PerformanceHistoryDataKeys
7
7
 
8
- class_attribute :stats_calculator_class, :performance_data_class
8
+ class_attribute :stats_calculator_class, :performance_data_class, :authority_list_class
9
9
  self.stats_calculator_class = QaServer::PerformanceCalculatorService
10
10
  self.performance_data_class = QaServer::PerformanceHistory
11
+ self.authority_list_class = QaServer::AuthorityListerService
11
12
 
12
- # Get hourly average for the past 24 hours.
13
- # @param authority_name [String] limit statistics to records for the given authority (default: all authorities)
14
- # @param action [Symbol] one of :search, :fetch, :all_actions
15
- # @param force [Boolean] if true, forces cache to regenerate; otherwise, returns value from cache unless expired
13
+ # Performance data for a day, a month, a year, and all time for each authority.
14
+ # @param datatype [Symbol] what type of data should be calculated (e.g. :datatable, :graph, :all)
16
15
  # @returns [Hash] performance statistics for the past 24 hours
17
16
  # @example
18
- # { 0: { hour: '1400', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
19
- # 1: { hour: '1500', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
20
- # 2: { hour: '1600', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
21
- # ...,
22
- # 23: { hour: 'NOW', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }}
17
+ # { all_authorities:
18
+ # { search:
19
+ # {
20
+ # day:
21
+ # { 0: { hour: '1400', stats: { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
22
+ # 1: { hour: '1500', stats: { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
23
+ # 2: { hour: '1600', stats: { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
24
+ # ...,
25
+ # 23: { hour: 'NOW', retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }}
26
+ # },
27
+ # month:
28
+ # { 0: { day: '07-15-2019', stats: { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
29
+ # 1: { day: '07-16-2019', stats: { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
30
+ # 2: { day: '07-17-2019', stats: { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
31
+ # ...,
32
+ # 29: { day: 'TODAY', stats: { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }}
33
+ # },
34
+ # year:
35
+ # { 0: { month: '09-2019', stats: { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
36
+ # 1: { month: '10-2019', stats: { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
37
+ # 2: { month: '11-2019', stats: { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
38
+ # ...,
39
+ # 11: { month: '08-2019', stats: { retrieve_avg_ms: 12.3, graph_load_avg_ms: 2.1, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }}
40
+ # }
41
+ # },
42
+ # fetch: { ... # same data as for search_stats },
43
+ # all: { ... # same data as for search_stats }
44
+ # },
45
+ # AGROVOC_LD4L_CACHE: { ... # same data for each authority }
23
46
  # }
24
- def average_last_24_hours(authority_name: nil, action: nil, force: false)
25
- Rails.cache.fetch("#{self.class}/#{__method__}/#{authority_name || ALL_AUTH}/#{action}/#{FOR_DAY}",
26
- expires_in: QaServer.current_time.end_of_hour - QaServer.current_time,
27
- race_condition_ttl: 1.hour, force: force) do
28
- Rails.logger.info("#{self.class}##{__method__} - calculating performance stats for last 24 hours - cache expired or refresh requested (#{force})")
29
- calculate_last_24_hours(authority_name, action)
30
- end
47
+ def calculate_graph_data(force:)
48
+ QaServer.config.monitor_logger.info("(QaServer::PerformanceGraphDataService##{__method__}) - calculating performance graph data")
49
+ QaServer.config.performance_cache.write_all
50
+ data = {}
51
+ auths = authority_list_class.authorities_list
52
+ calculate_all = force || cache_expired?
53
+ data[ALL_AUTH] = graph_data_for_authority(force: force, calculate_all: calculate_all)
54
+ auths.each { |auth_name| data[auth_name] = graph_data_for_authority(authority_name: auth_name, force: force, calculate_all: calculate_all) }
55
+ data
31
56
  end
32
57
 
33
- # Get daily average for the past 30 days.
34
- # @param authority_name [String] limit statistics to records for the given authority (default: all authorities)
35
- # @param action [Symbol] one of :search, :fetch, :all_actions
36
- # @param force [Boolean] if true, forces cache to regenerate; otherwise, returns value from cache unless expired
37
- # @returns [Hash] performance statistics for the past 30 days
38
- # @example
39
- # { 0: { day: '07-15-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
40
- # 1: { day: '07-16-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
41
- # 2: { day: '07-17-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
42
- # ...,
43
- # 29: { day: 'TODAY', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }}
44
- # }
45
- def average_last_30_days(authority_name: nil, action: nil, force: false)
46
- Rails.cache.fetch("#{self.class}/#{__method__}/#{authority_name || ALL_AUTH}/#{action}/#{FOR_MONTH}",
47
- expires_in: QaServer.cache_expiry, race_condition_ttl: 1.hour, force: force) do
48
- Rails.logger.info("#{self.class}##{__method__} - calculating performance stats for last 30 days - cache expired or refresh requested (#{force})")
49
- calculate_last_30_days(authority_name, action)
58
+ private
59
+
60
+ def graph_data_for_authority(authority_name: nil, force:, calculate_all:)
61
+ [:search, :fetch, :all_actions].each_with_object({}) do |action, hash|
62
+ data = {}
63
+ data[FOR_DAY] = average_last_24_hours(authority_name: authority_name, action: action, force: force)
64
+ data[FOR_MONTH] = average_last_30_days(authority_name: authority_name, action: action, force: force) if calculate_all
65
+ data[FOR_YEAR] = average_last_12_months(authority_name: authority_name, action: action, force: force) if calculate_all
66
+ hash[action] = data
67
+ end
50
68
  end
51
- end
52
69
 
53
- # Get daily average for the past 12 months.
54
- # @param authority_name [String] limit statistics to records for the given authority (default: all authorities)
55
- # @param action [Symbol] one of :search, :fetch, :all_actions
56
- # @param force [Boolean] if true, forces cache to regenerate; otherwise, returns value from cache unless expired
57
- # @returns [Hash] performance statistics for the past 12 months
58
- # @example
59
- # { 0: { month: '09-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
60
- # 1: { month: '10-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
61
- # 2: { month: '11-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
62
- # ...,
63
- # 11: { month: '08-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }}
64
- # }
65
- def average_last_12_months(authority_name: nil, action: nil, force: false)
66
- Rails.cache.fetch("#{self.class}/#{__method__}/#{authority_name || ALL_AUTH}/#{action}/#{FOR_YEAR}",
67
- expires_in: QaServer.cache_expiry, race_condition_ttl: 1.hour, force: force) do
68
- Rails.logger.info("#{self.class}##{__method__} - calculating performance stats for last 12 months - cache expired or refresh requested (#{force})")
69
- calculate_last_12_months(authority_name, action)
70
+ # Get hourly average for the past 24 hours.
71
+ # @param authority_name [String] limit statistics to records for the given authority (default: all authorities)
72
+ # @param action [Symbol] one of :search, :fetch, :all_actions
73
+ # @param force [Boolean] if true, forces cache to regenerate; otherwise, returns value from cache unless expired
74
+ # @returns [Hash] performance statistics for the past 24 hours
75
+ # @example
76
+ # { 0: { hour: '1400', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
77
+ # 1: { hour: '1500', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
78
+ # 2: { hour: '1600', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
79
+ # ...,
80
+ # 23: { hour: 'NOW', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }}
81
+ # }
82
+ def average_last_24_hours(authority_name: nil, action: nil, force: false)
83
+ avgs = Rails.cache.fetch("QaServer::PerformanceGraphDataService/#{__method__}/#{authority_name || ALL_AUTH}/#{action}/#{FOR_DAY}",
84
+ expires_in: QaServer::TimeService.current_time.end_of_hour - QaServer::TimeService.current_time,
85
+ race_condition_ttl: 1.hour, force: force) do
86
+ QaServer.config.monitor_logger.info("(QaServer::PerformanceGraphDataService##{__method__}) - calculating performance stats - cache expired or refresh requested (force: #{force})")
87
+ calculate_last_24_hours(authority_name, action)
88
+ end
89
+ calculate_last_hour(authority_name, action, avgs)
70
90
  end
71
- end
72
91
 
73
- private
92
+ # Get daily average for the past 30 days.
93
+ # @param authority_name [String] limit statistics to records for the given authority (default: all authorities)
94
+ # @param action [Symbol] one of :search, :fetch, :all_actions
95
+ # @param force [Boolean] if true, forces cache to regenerate; otherwise, returns value from cache unless expired
96
+ # @returns [Hash] performance statistics for the past 30 days
97
+ # @example
98
+ # { 0: { day: '07-15-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
99
+ # 1: { day: '07-16-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
100
+ # 2: { day: '07-17-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
101
+ # ...,
102
+ # 29: { day: 'TODAY', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }}
103
+ # }
104
+ def average_last_30_days(authority_name: nil, action: nil, force: false)
105
+ Rails.cache.fetch("QaServer::PerformanceGraphDataService/#{__method__}/#{authority_name || ALL_AUTH}/#{action}/#{FOR_MONTH}",
106
+ expires_in: QaServer::MonitorCacheService.cache_expiry, race_condition_ttl: 1.hour, force: force) do
107
+ QaServer.config.monitor_logger.info("(QaServer::PerformanceGraphDataService##{__method__}) - calculating performance stats - cache expired or refresh requested (force: #{force})")
108
+ calculate_last_30_days(authority_name, action)
109
+ end
110
+ end
111
+
112
+ # Get daily average for the past 12 months.
113
+ # @param authority_name [String] limit statistics to records for the given authority (default: all authorities)
114
+ # @param action [Symbol] one of :search, :fetch, :all_actions
115
+ # @param force [Boolean] if true, forces cache to regenerate; otherwise, returns value from cache unless expired
116
+ # @returns [Hash] performance statistics for the past 12 months
117
+ # @example
118
+ # { 0: { month: '09-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
119
+ # 1: { month: '10-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
120
+ # 2: { month: '11-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }},
121
+ # ...,
122
+ # 11: { month: '08-2019', stats: { load_avg_ms: 12.3, normalization_avg_ms: 4.2, full_request_avg_ms: 16.5, etc. }}
123
+ # }
124
+ def average_last_12_months(authority_name: nil, action: nil, force: false)
125
+ Rails.cache.fetch("QaServer::PerformanceGraphDataService/#{__method__}/#{authority_name || ALL_AUTH}/#{action}/#{FOR_YEAR}",
126
+ expires_in: QaServer::MonitorCacheService.cache_expiry, race_condition_ttl: 1.hour, force: force) do
127
+ QaServer.config.monitor_logger.info("(QaServer::PerformanceGraphDataService##{__method__}) - calculating performance stats - cache expired or refresh requested (force: #{force})")
128
+ calculate_last_12_months(authority_name, action)
129
+ end
130
+ end
74
131
 
75
132
  def records_by(authority_name, action, time_period)
76
133
  where_clause = { dt_stamp: time_period }
@@ -99,49 +156,50 @@ module QaServer
99
156
  end
100
157
  end
101
158
 
159
+ def calculate_from_records(records, range_idx, range_label)
160
+ stats = stats_calculator_class.new(records).calculate_average_stats
161
+ { STATS => stats, range_idx => range_label }
162
+ end
163
+
164
+ def calculate_last_hour(authority_name, action, avgs)
165
+ start_hour = QaServer::TimeService.current_time.beginning_of_hour
166
+ records = records_by(authority_name, action, start_hour..start_hour.end_of_hour)
167
+ avgs[23] = calculate_from_records(records, BY_HOUR, performance_by_hour_label(23, start_hour))
168
+ avgs
169
+ end
170
+
102
171
  def calculate_last_24_hours(authority_name, action)
103
- start_hour = QaServer.current_time.beginning_of_hour - 23.hours
104
- avgs = {}
105
- 0.upto(23).each do |idx|
172
+ start_hour = QaServer::TimeService.current_time.beginning_of_hour - 23.hours
173
+ 0.upto(23).each_with_object({}) do |idx, avgs|
106
174
  records = records_by(authority_name, action, start_hour..start_hour.end_of_hour)
107
- stats = stats_calculator_class.new(records).calculate_stats(avg: true, full: false)
108
- data = {}
109
- data[BY_HOUR] = performance_by_hour_label(idx, start_hour)
110
- data[STATS] = stats
111
- avgs[idx] = data
175
+ avgs[idx] = calculate_from_records(records, BY_HOUR, performance_by_hour_label(idx, start_hour))
112
176
  start_hour += 1.hour
113
177
  end
114
- avgs
115
178
  end
116
179
 
117
180
  def calculate_last_30_days(authority_name, action)
118
- start_day = QaServer.current_time.beginning_of_day - 29.days
119
- avgs = {}
120
- 0.upto(29).each do |idx|
181
+ start_day = QaServer::TimeService.current_time.beginning_of_day - 29.days
182
+ 0.upto(29).each_with_object({}) do |idx, avgs|
121
183
  records = records_by(authority_name, action, start_day..start_day.end_of_day)
122
- stats = stats_calculator_class.new(records).calculate_stats(avg: true, full: false)
123
- data = {}
124
- data[BY_DAY] = performance_by_day_label(idx, start_day)
125
- data[STATS] = stats
126
- avgs[idx] = data
184
+ avgs[idx] = calculate_from_records(records, BY_DAY, performance_by_day_label(idx, start_day))
127
185
  start_day += 1.day
128
186
  end
129
- avgs
130
187
  end
131
188
 
132
189
  def calculate_last_12_months(authority_name, action)
133
- start_month = QaServer.current_time.beginning_of_month - 11.months
134
- avgs = {}
135
- 0.upto(11).each do |idx|
190
+ start_month = QaServer::TimeService.current_time.beginning_of_month - 11.months
191
+ 0.upto(11).each_with_object({}) do |idx, avgs|
136
192
  records = records_by(authority_name, action, start_month..start_month.end_of_month)
137
- stats = stats_calculator_class.new(records).calculate_stats(avg: true, full: false)
138
- data = {}
139
- data[BY_MONTH] = start_month.strftime("%m-%Y")
140
- data[STATS] = stats
141
- avgs[idx] = data
193
+ avgs[idx] = calculate_from_records(records, BY_MONTH, start_month.strftime("%m-%Y"))
142
194
  start_month += 1.month
143
195
  end
144
- avgs
196
+ end
197
+
198
+ # @returns [Boolean] true if cache has expired; otherwise, false
199
+ def cache_expired?
200
+ expired = Rails.cache.fetch("QaServer::PerformanceGraphDataService/#{__method__}", expires_in: 5.seconds) { true }
201
+ Rails.cache.fetch("QaServer::PerformanceGraphDataService/#{__method__}", expires_in: QaServer::MonitorCacheService.cache_expiry, force: expired) { false } # reset if expired
202
+ expired
145
203
  end
146
204
  end
147
205
  end