qa_server 5.5.1 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
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