dbwatcher 1.1.1 → 1.1.3

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -2
  3. data/app/assets/config/dbwatcher_manifest.js +1 -0
  4. data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +196 -119
  5. data/app/assets/javascripts/dbwatcher/components/dashboard.js +325 -0
  6. data/app/assets/javascripts/dbwatcher/components/timeline.js +211 -0
  7. data/app/assets/javascripts/dbwatcher/dbwatcher.js +5 -0
  8. data/app/assets/stylesheets/dbwatcher/application.css +691 -41
  9. data/app/assets/stylesheets/dbwatcher/application.scss +5 -0
  10. data/app/assets/stylesheets/dbwatcher/components/_badges.scss +68 -23
  11. data/app/assets/stylesheets/dbwatcher/components/_compact_table.scss +83 -26
  12. data/app/assets/stylesheets/dbwatcher/components/_diagrams.scss +3 -3
  13. data/app/assets/stylesheets/dbwatcher/components/_navigation.scss +9 -0
  14. data/app/assets/stylesheets/dbwatcher/components/_tabulator.scss +248 -0
  15. data/app/assets/stylesheets/dbwatcher/components/_timeline.scss +326 -0
  16. data/app/assets/stylesheets/dbwatcher/vendor/_tabulator_overrides.scss +37 -0
  17. data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +18 -4
  18. data/app/controllers/dbwatcher/api/v1/system_info_controller.rb +180 -0
  19. data/app/controllers/dbwatcher/dashboard/system_info_controller.rb +64 -0
  20. data/app/controllers/dbwatcher/dashboard_controller.rb +17 -0
  21. data/app/controllers/dbwatcher/sessions_controller.rb +3 -19
  22. data/app/helpers/dbwatcher/application_helper.rb +43 -11
  23. data/app/helpers/dbwatcher/diagram_helper.rb +0 -88
  24. data/app/views/dbwatcher/dashboard/_layout.html.erb +27 -0
  25. data/app/views/dbwatcher/dashboard/_overview.html.erb +188 -0
  26. data/app/views/dbwatcher/dashboard/_system_info.html.erb +22 -0
  27. data/app/views/dbwatcher/dashboard/_system_info_content.html.erb +389 -0
  28. data/app/views/dbwatcher/dashboard/index.html.erb +8 -177
  29. data/app/views/dbwatcher/sessions/_layout.html.erb +26 -0
  30. data/app/views/dbwatcher/sessions/{_summary_tab.html.erb → _summary.html.erb} +1 -1
  31. data/app/views/dbwatcher/sessions/_tables.html.erb +170 -0
  32. data/app/views/dbwatcher/sessions/_timeline.html.erb +260 -0
  33. data/app/views/dbwatcher/sessions/index.html.erb +107 -87
  34. data/app/views/dbwatcher/sessions/show.html.erb +12 -4
  35. data/app/views/dbwatcher/tables/index.html.erb +32 -40
  36. data/app/views/layouts/dbwatcher/application.html.erb +101 -48
  37. data/config/routes.rb +25 -7
  38. data/lib/dbwatcher/configuration.rb +18 -1
  39. data/lib/dbwatcher/services/analyzers/table_summary_builder.rb +102 -1
  40. data/lib/dbwatcher/services/api/{changes_data_service.rb → tables_data_service.rb} +6 -6
  41. data/lib/dbwatcher/services/base_service.rb +2 -0
  42. data/lib/dbwatcher/services/system_info/database_info_collector.rb +263 -0
  43. data/lib/dbwatcher/services/system_info/machine_info_collector.rb +387 -0
  44. data/lib/dbwatcher/services/system_info/runtime_info_collector.rb +328 -0
  45. data/lib/dbwatcher/services/system_info/system_info_collector.rb +114 -0
  46. data/lib/dbwatcher/services/timeline_data_service/enhancement_utilities.rb +100 -0
  47. data/lib/dbwatcher/services/timeline_data_service/entry_builder.rb +125 -0
  48. data/lib/dbwatcher/services/timeline_data_service/metadata_builder.rb +93 -0
  49. data/lib/dbwatcher/services/timeline_data_service.rb +130 -0
  50. data/lib/dbwatcher/storage/api/concerns/table_analyzer.rb +1 -1
  51. data/lib/dbwatcher/storage/concerns/error_handler.rb +6 -6
  52. data/lib/dbwatcher/storage/session.rb +5 -0
  53. data/lib/dbwatcher/storage/system_info_storage.rb +242 -0
  54. data/lib/dbwatcher/storage.rb +12 -0
  55. data/lib/dbwatcher/version.rb +1 -1
  56. data/lib/dbwatcher.rb +16 -2
  57. metadata +28 -16
  58. data/app/helpers/dbwatcher/component_helper.rb +0 -29
  59. data/app/views/dbwatcher/sessions/_changes_tab.html.erb +0 -265
  60. data/app/views/dbwatcher/sessions/_tab_navigation.html.erb +0 -12
  61. data/app/views/dbwatcher/sessions/changes.html.erb +0 -21
  62. data/app/views/dbwatcher/sessions/components/changes/_filters.html.erb +0 -44
  63. data/app/views/dbwatcher/sessions/components/changes/_table_list.html.erb +0 -96
  64. data/app/views/dbwatcher/sessions/diagrams.html.erb +0 -21
  65. data/app/views/dbwatcher/sessions/shared/_layout.html.erb +0 -8
  66. data/app/views/dbwatcher/sessions/shared/_navigation.html.erb +0 -35
  67. data/app/views/dbwatcher/sessions/shared/_session_header.html.erb +0 -25
  68. data/app/views/dbwatcher/sessions/summary.html.erb +0 -21
  69. /data/app/views/dbwatcher/sessions/{_diagrams_tab.html.erb → _diagrams.html.erb} +0 -0
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbwatcher
4
+ module Services
5
+ class TimelineDataService
6
+ # Module for building timeline metadata
7
+ module MetadataBuilder
8
+ private
9
+
10
+ # Build timeline metadata
11
+ #
12
+ # @return [Hash] timeline metadata
13
+ def build_timeline_metadata
14
+ return {} if @timeline_entries.empty?
15
+
16
+ {
17
+ total_operations: @timeline_entries.length,
18
+ time_range: calculate_time_range,
19
+ session_duration: calculate_session_duration,
20
+ tables_affected: extract_affected_tables,
21
+ operation_counts: count_operations_by_type,
22
+ peak_activity_periods: find_peak_activity_periods
23
+ }
24
+ end
25
+
26
+ # Calculate time range for the session
27
+ #
28
+ # @return [Hash] time range with start and end
29
+ def calculate_time_range
30
+ return {} if @timeline_entries.empty?
31
+
32
+ start_time = Time.at(@timeline_entries.first[:raw_timestamp])
33
+ end_time = Time.at(@timeline_entries.last[:raw_timestamp])
34
+
35
+ {
36
+ start: start_time.iso8601,
37
+ end: end_time.iso8601
38
+ }
39
+ end
40
+
41
+ # Calculate total session duration
42
+ #
43
+ # @return [String] formatted session duration
44
+ def calculate_session_duration
45
+ return "00:00" if @timeline_entries.length < 2
46
+
47
+ duration = @timeline_entries.last[:raw_timestamp] - @timeline_entries.first[:raw_timestamp]
48
+ format_duration(duration)
49
+ end
50
+
51
+ # Extract list of affected tables
52
+ #
53
+ # @return [Array<String>] unique table names
54
+ def extract_affected_tables
55
+ @timeline_entries.map { |entry| entry[:table_name] }.uniq.sort
56
+ end
57
+
58
+ # Count operations by type
59
+ #
60
+ # @return [Hash] operation counts
61
+ def count_operations_by_type
62
+ @timeline_entries.group_by { |entry| entry[:operation] }
63
+ .transform_values(&:count)
64
+ end
65
+
66
+ # Find peak activity periods
67
+ #
68
+ # @return [Array<Hash>] peak activity periods
69
+ def find_peak_activity_periods
70
+ return [] if @timeline_entries.length < 10
71
+
72
+ # Group operations by 1-minute windows
73
+ windows = @timeline_entries.group_by do |entry|
74
+ timestamp = Time.at(entry[:raw_timestamp])
75
+ Time.new(timestamp.year, timestamp.month, timestamp.day, timestamp.hour, timestamp.min, 0)
76
+ end
77
+
78
+ # Find windows with more than average activity
79
+ average_ops = @timeline_entries.length / windows.length.to_f
80
+
81
+ windows.select { |_, ops| ops.length > average_ops * 1.5 }
82
+ .map do |window_start, ops|
83
+ {
84
+ start: window_start.iso8601,
85
+ end: (window_start + 1.minute).iso8601,
86
+ operations_count: ops.length
87
+ }
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "timeline_data_service/metadata_builder"
4
+ require_relative "timeline_data_service/entry_builder"
5
+ require_relative "timeline_data_service/enhancement_utilities"
6
+
7
+ module Dbwatcher
8
+ module Services
9
+ # Timeline Data Service for processing session data into chronological timeline format
10
+ #
11
+ # This service transforms session changes into a chronologically ordered timeline
12
+ # with enhanced metadata for visualization and filtering.
13
+ #
14
+ # @example
15
+ # service = TimelineDataService.new(session)
16
+ # result = service.call
17
+ # timeline = result[:timeline]
18
+ # metadata = result[:metadata]
19
+ class TimelineDataService
20
+ include MetadataBuilder
21
+ include EntryBuilder
22
+ include EnhancementUtilities
23
+ # Initialize the timeline data service
24
+ #
25
+ # @param session [Session] session object containing changes data
26
+ def initialize(session)
27
+ @session = session
28
+ @timeline_entries = []
29
+ @start_time = Time.current
30
+ end
31
+
32
+ # Process session data into timeline format
33
+ #
34
+ # @return [Hash] processed timeline data with metadata
35
+ def call
36
+ Rails.logger.info("Processing timeline data for session #{@session.id}")
37
+
38
+ validate_session_data
39
+ build_timeline_entries
40
+ sort_chronologically
41
+ enhance_with_metadata
42
+ result = build_result
43
+
44
+ Rails.logger.info(
45
+ "Timeline processing completed for session #{@session.id} (#{@timeline_entries.length} entries)"
46
+ )
47
+
48
+ result
49
+ rescue StandardError => e
50
+ Rails.logger.error("Timeline processing failed for session #{@session.id}: #{e.message}")
51
+ build_error_result(e)
52
+ end
53
+
54
+ private
55
+
56
+ # Validate session data before processing
57
+ #
58
+ # @raise [ArgumentError] if session data is invalid
59
+ def validate_session_data
60
+ raise ArgumentError, "Session is required" unless @session
61
+ raise ArgumentError, "Session ID is required" unless @session.id
62
+ raise ArgumentError, "Session changes are required" unless @session.changes
63
+ end
64
+
65
+ # Build timeline entries from session changes
66
+ #
67
+ # @return [void]
68
+ def build_timeline_entries
69
+ @session.changes.each_with_index do |change, index|
70
+ next unless valid_change?(change)
71
+
72
+ @timeline_entries << create_timeline_entry(change, index)
73
+ end
74
+ end
75
+
76
+ # Check if a change is valid for timeline processing
77
+ #
78
+ # @param change [Hash] change data
79
+ # @return [Boolean] true if change is valid
80
+ def valid_change?(change)
81
+ change.is_a?(Hash) &&
82
+ change[:table_name] &&
83
+ change[:operation] &&
84
+ change[:timestamp]
85
+ end
86
+
87
+ # Sort timeline entries chronologically
88
+ #
89
+ # @return [void]
90
+ def sort_chronologically
91
+ @timeline_entries.sort_by! { |entry| entry[:raw_timestamp] }
92
+ end
93
+
94
+ # Build final result hash
95
+ #
96
+ # @return [Hash] complete timeline result
97
+ def build_result
98
+ {
99
+ timeline: @timeline_entries,
100
+ metadata: build_timeline_metadata,
101
+ summary: build_timeline_summary,
102
+ errors: []
103
+ }
104
+ end
105
+
106
+ # Build timeline summary
107
+ #
108
+ # @return [Hash] timeline summary
109
+ def build_timeline_summary
110
+ {
111
+ total_entries: @timeline_entries.length,
112
+ processing_time: (Time.current - @start_time).round(3)
113
+ }
114
+ end
115
+
116
+ # Build error result
117
+ #
118
+ # @param error [StandardError] error that occurred
119
+ # @return [Hash] error result
120
+ def build_error_result(error)
121
+ {
122
+ timeline: [],
123
+ metadata: {},
124
+ summary: { error: error.message },
125
+ errors: [{ type: "processing_error", message: error.message }]
126
+ }
127
+ end
128
+ end
129
+ end
130
+ end
@@ -24,7 +24,7 @@ module Dbwatcher
24
24
  # @return [Hash] tables summary hash
25
25
  def build_tables_summary(session)
26
26
  # Delegate to new service while maintaining interface compatibility
27
- Dbwatcher::Services::Analyzers::TableSummaryBuilder.call(session)
27
+ Dbwatcher::Services::Analyzers::TableSummaryBuilder.new(session).call
28
28
  end
29
29
 
30
30
  # Process all changes in a session (legacy method for backward compatibility)
@@ -28,16 +28,16 @@ module Dbwatcher
28
28
  def safe_operation(operation_name, default_value = nil, &block)
29
29
  block.call
30
30
  rescue JSON::ParserError => e
31
- log_error("JSON parsing failed in #{operation_name}", e)
31
+ log_error_with_exception("JSON parsing failed in #{operation_name}", e)
32
32
  default_value
33
33
  rescue Errno::ENOENT => e
34
- log_error("File not found in #{operation_name}", e)
34
+ log_error_with_exception("File not found in #{operation_name}", e)
35
35
  default_value
36
36
  rescue Errno::EACCES => e
37
- log_error("Permission denied in #{operation_name}", e)
37
+ log_error_with_exception("Permission denied in #{operation_name}", e)
38
38
  raise StorageError, "Permission denied: #{e.message}"
39
39
  rescue StandardError => e
40
- log_error("#{operation_name} failed", e)
40
+ log_error_with_exception("#{operation_name} failed", e)
41
41
  default_value
42
42
  end
43
43
 
@@ -51,7 +51,7 @@ module Dbwatcher
51
51
  block.call
52
52
  rescue StandardError => e
53
53
  error_message = "Storage #{operation} failed: #{e.message}"
54
- log_error(error_message, e)
54
+ log_error_with_exception(error_message, e)
55
55
  raise StorageError, error_message
56
56
  end
57
57
 
@@ -62,7 +62,7 @@ module Dbwatcher
62
62
  # @param message [String] the error message
63
63
  # @param error [Exception] the exception that occurred
64
64
  # @return [void]
65
- def log_error(message, error)
65
+ def log_error_with_exception(message, error)
66
66
  if defined?(Rails) && Rails.logger
67
67
  Rails.logger.warn("#{message}: #{error.message}")
68
68
  else
@@ -28,6 +28,11 @@ module Dbwatcher
28
28
  }
29
29
  end
30
30
 
31
+ # Used by Rails URL helpers to convert the object to a URL parameter
32
+ def to_param
33
+ id.to_s
34
+ end
35
+
31
36
  def summary
32
37
  return {} unless changes.is_a?(Array)
33
38
 
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../services/system_info/system_info_collector"
4
+ require_relative "../logging"
5
+
6
+ module Dbwatcher
7
+ module Storage
8
+ # System information storage class
9
+ #
10
+ # Handles storage, caching, and retrieval of system information data.
11
+ # Provides intelligent caching with configurable TTL and refresh capabilities.
12
+ #
13
+ # @example
14
+ # storage = SystemInfoStorage.new
15
+ # info = storage.cached_info
16
+ # storage.refresh_info
17
+ #
18
+ # This class is necessarily complex due to the comprehensive system information
19
+ # storage and retrieval functionality it provides.
20
+ # rubocop:disable Metrics/ClassLength
21
+ class SystemInfoStorage < BaseStorage
22
+ include Dbwatcher::Logging
23
+ # Initialize system info storage
24
+ def initialize
25
+ super
26
+ @info_file = File.join(storage_path, "system_info.json")
27
+ end
28
+
29
+ # Save system information to storage
30
+ #
31
+ # @param info [Hash] system information data
32
+ # @return [Boolean] true if successful
33
+ def save_info(info)
34
+ # Convert all keys to strings before saving to ensure consistent format
35
+ info_with_string_keys = convert_keys_to_strings(info)
36
+ safe_write_json(@info_file, info_with_string_keys)
37
+ end
38
+
39
+ # Load system information from storage
40
+ #
41
+ # @return [Hash] system information data or empty hash
42
+ def load_info
43
+ # Override the base class default to return {} instead of []
44
+ safe_operation("read JSON from #{@info_file}", {}) do
45
+ result = file_manager.read_json(@info_file)
46
+ result = {} if result.is_a?(Array) && result.empty?
47
+ # Convert string keys back to symbols for consistent access in the app
48
+ convert_keys_to_symbols(result)
49
+ end
50
+ end
51
+
52
+ # Refresh system information by collecting new data
53
+ #
54
+ # @return [Hash] refreshed system information
55
+ def refresh_info
56
+ log_info "Refreshing system information"
57
+
58
+ info = Services::SystemInfo::SystemInfoCollector.call
59
+ save_info(info)
60
+
61
+ log_info "System information refreshed successfully"
62
+ # Return the info with symbol keys for consistent access
63
+ convert_keys_to_symbols(info)
64
+ rescue StandardError => e
65
+ log_error "Failed to refresh system information: #{e.message}"
66
+
67
+ # Return cached info if available, otherwise empty hash with error
68
+ cached_info = load_info
69
+ cached_info.empty? ? { error: e.message } : cached_info
70
+ end
71
+
72
+ # Get cached system information with TTL support
73
+ #
74
+ # @param max_age [Integer] maximum age in seconds (default: from config)
75
+ # @return [Hash] cached or refreshed system information
76
+ def cached_info(max_age: nil)
77
+ max_age ||= Dbwatcher.configuration.system_info_cache_duration
78
+
79
+ info = load_info
80
+
81
+ # If no cached info exists, collect new data
82
+ return refresh_info if info.empty?
83
+
84
+ # Check if cached info is expired
85
+ if info_expired?(info, max_age)
86
+ log_info "System information cache expired, refreshing"
87
+ return refresh_info
88
+ end
89
+
90
+ log_info "Using cached system information"
91
+ info
92
+ rescue StandardError => e
93
+ log_error "Failed to get cached system information: #{e.message}"
94
+ { error: e.message }
95
+ end
96
+
97
+ # Check if system information is available
98
+ #
99
+ # @return [Boolean] true if system information exists
100
+ def info_available?
101
+ !load_info.empty?
102
+ rescue StandardError => e
103
+ log_error "Failed to check info availability: #{e.message}"
104
+ false
105
+ end
106
+
107
+ # Get system information age in seconds
108
+ #
109
+ # @return [Integer, nil] age in seconds or nil if not available
110
+ def info_age
111
+ info = load_info
112
+ return nil if info.empty? || !info[:collected_at]
113
+
114
+ collected_at = info[:collected_at]
115
+ current_time - Time.parse(collected_at)
116
+ rescue StandardError => e
117
+ log_error "Failed to get info age: #{e.message}"
118
+ nil
119
+ end
120
+
121
+ # Clear cached system information
122
+ #
123
+ # @return [Boolean] true if successful
124
+ def clear_cache
125
+ log_info "Clearing system information cache"
126
+ safe_delete_file(@info_file)
127
+ end
128
+
129
+ # Get system information summary for dashboard
130
+ #
131
+ # @return [Hash] summary information
132
+ # rubocop:disable Metrics/MethodLength
133
+ def summary
134
+ info = cached_info
135
+ return {} if info.empty? || info[:error]
136
+
137
+ {
138
+ hostname: dig_with_indifferent_access(info, :machine, :hostname),
139
+ os: dig_with_indifferent_access(info, :machine, :os, :name),
140
+ ruby_version: dig_with_indifferent_access(info, :runtime, :ruby_version),
141
+ rails_version: dig_with_indifferent_access(info, :runtime, :rails_version),
142
+ database_adapter: dig_with_indifferent_access(info, :database, :adapter, :name),
143
+ memory_usage: dig_with_indifferent_access(info, :machine, :memory, :usage_percent),
144
+ cpu_load: dig_with_indifferent_access(info, :machine, :load_average, "1min") ||
145
+ dig_with_indifferent_access(info, :machine, :cpu, :load_average, "1min") ||
146
+ dig_with_indifferent_access(info, :machine, :load, :one_minute),
147
+ active_connections: dig_with_indifferent_access(info, :database, :active_connections) ||
148
+ dig_with_indifferent_access(info, :database, :connection_pool, :connections),
149
+ collected_at: info[:collected_at],
150
+ collection_duration: info[:collection_duration]
151
+ }
152
+ rescue StandardError => e
153
+ log_error "Failed to get system info summary: #{e.message}"
154
+ {}
155
+ end
156
+ # rubocop:enable Metrics/MethodLength
157
+
158
+ private
159
+
160
+ # Check if system information is expired
161
+ #
162
+ # @param info [Hash] system information data
163
+ # @param max_age [Integer] maximum age in seconds
164
+ # @return [Boolean] true if expired
165
+ def info_expired?(info, max_age)
166
+ collected_at = info[:collected_at]
167
+ return true unless collected_at
168
+
169
+ (current_time - Time.parse(collected_at)) > max_age
170
+ rescue StandardError => e
171
+ log_error "Failed to check info expiration: #{e.message}"
172
+ true # Assume expired on error
173
+ end
174
+
175
+ # Get current time, using Rails method if available
176
+ #
177
+ # @return [Time] current time
178
+ def current_time
179
+ defined?(Time.current) ? Time.current : Time.now
180
+ end
181
+
182
+ # Convert all hash keys to strings recursively
183
+ #
184
+ # @param hash [Hash] hash to convert
185
+ # @return [Hash] hash with string keys
186
+ def convert_keys_to_strings(hash)
187
+ return hash unless hash.is_a?(Hash)
188
+
189
+ hash.each_with_object({}) do |(key, value), result|
190
+ string_key = key.to_s
191
+ result[string_key] = if value.is_a?(Hash)
192
+ convert_keys_to_strings(value)
193
+ elsif value.is_a?(Array)
194
+ value.map { |v| v.is_a?(Hash) ? convert_keys_to_strings(v) : v }
195
+ else
196
+ value
197
+ end
198
+ end
199
+ end
200
+
201
+ # Convert all hash keys to symbols recursively
202
+ #
203
+ # @param hash [Hash] hash to convert
204
+ # @return [Hash] hash with symbol keys
205
+ def convert_keys_to_symbols(hash)
206
+ return hash unless hash.is_a?(Hash)
207
+
208
+ hash.each_with_object({}) do |(key, value), result|
209
+ symbol_key = key.to_s.to_sym
210
+ result[symbol_key] = if value.is_a?(Hash)
211
+ convert_keys_to_symbols(value)
212
+ elsif value.is_a?(Array)
213
+ value.map { |v| v.is_a?(Hash) ? convert_keys_to_symbols(v) : v }
214
+ else
215
+ value
216
+ end
217
+ end
218
+ end
219
+
220
+ # Safe access to nested hash values with indifferent access
221
+ #
222
+ # @param hash [Hash] hash to access
223
+ # @param keys [Array] keys to access
224
+ # @return [Object, nil] value or nil if not found
225
+ def dig_with_indifferent_access(hash, *keys)
226
+ return nil unless hash.is_a?(Hash)
227
+
228
+ current = hash
229
+ keys.each do |key|
230
+ key_sym = key.to_s.to_sym
231
+ key_str = key.to_s
232
+ return nil unless current.is_a?(Hash)
233
+
234
+ current = current[key_sym] || current[key_str]
235
+ return nil if current.nil?
236
+ end
237
+ current
238
+ end
239
+ end
240
+ # rubocop:enable Metrics/ClassLength
241
+ end
242
+ end
@@ -9,6 +9,7 @@ require_relative "storage/session_storage"
9
9
  require_relative "storage/query_storage"
10
10
  require_relative "storage/table_storage"
11
11
  require_relative "storage/session_query"
12
+ require_relative "storage/system_info_storage"
12
13
  require_relative "storage/api/base_api"
13
14
  require_relative "storage/api/query_api"
14
15
  require_relative "storage/api/table_api"
@@ -68,6 +69,16 @@ module Dbwatcher
68
69
  @tables ||= Api::TableAPI.new(table_storage)
69
70
  end
70
71
 
72
+ # Provides access to system information operations
73
+ #
74
+ # @return [SystemInfoStorage] system info storage instance
75
+ # @example
76
+ # Dbwatcher::Storage.system_info.cached_info
77
+ # Dbwatcher::Storage.system_info.refresh_info
78
+ def system_info
79
+ @system_info ||= SystemInfoStorage.new
80
+ end
81
+
71
82
  # Resets all cached storage instances (primarily for testing)
72
83
  #
73
84
  # This method clears all memoized storage instances, forcing them
@@ -80,6 +91,7 @@ module Dbwatcher
80
91
  @session_storage = nil
81
92
  @query_storage = nil
82
93
  @table_storage = nil
94
+ @system_info = nil
83
95
  @sessions = nil
84
96
  @queries = nil
85
97
  @tables = nil
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dbwatcher
4
- VERSION = "1.1.1"
4
+ VERSION = "1.1.3"
5
5
  end
data/lib/dbwatcher.rb CHANGED
@@ -13,12 +13,12 @@ require_relative "dbwatcher/logging"
13
13
 
14
14
  # Storage layer
15
15
  require_relative "dbwatcher/storage"
16
+ require_relative "dbwatcher/middleware"
16
17
 
17
18
  # Tracking and SQL monitoring
18
19
  require_relative "dbwatcher/tracker"
19
20
  require_relative "dbwatcher/sql_logger"
20
21
  require_relative "dbwatcher/model_extension"
21
- require_relative "dbwatcher/middleware"
22
22
 
23
23
  # Base services
24
24
  require_relative "dbwatcher/services/base_service"
@@ -28,6 +28,12 @@ require_relative "dbwatcher/services/table_statistics_collector"
28
28
  require_relative "dbwatcher/services/dashboard_data_aggregator"
29
29
  require_relative "dbwatcher/services/query_filter_processor"
30
30
 
31
+ # System info services
32
+ require_relative "dbwatcher/services/system_info/machine_info_collector"
33
+ require_relative "dbwatcher/services/system_info/database_info_collector"
34
+ require_relative "dbwatcher/services/system_info/runtime_info_collector"
35
+ require_relative "dbwatcher/services/system_info/system_info_collector"
36
+
31
37
  # General analyzers
32
38
  require_relative "dbwatcher/services/analyzers/session_data_processor"
33
39
  require_relative "dbwatcher/services/analyzers/table_summary_builder"
@@ -68,23 +74,31 @@ require_relative "dbwatcher/services/diagram_system"
68
74
 
69
75
  # API services
70
76
  require_relative "dbwatcher/services/api/base_api_service"
71
- require_relative "dbwatcher/services/api/changes_data_service"
77
+ require_relative "dbwatcher/services/api/tables_data_service"
72
78
  require_relative "dbwatcher/services/api/summary_data_service"
73
79
  require_relative "dbwatcher/services/api/diagram_data_service"
74
80
 
75
81
  # Rails engine
76
82
  require_relative "dbwatcher/engine" if defined?(Rails)
77
83
 
84
+ # DBWatcher module
78
85
  module Dbwatcher
79
86
  class Error < StandardError; end
80
87
 
81
88
  class << self
82
89
  attr_writer :configuration
83
90
 
91
+ # Get configuration
92
+ #
93
+ # @return [Configuration] configuration
84
94
  def configuration
85
95
  @configuration ||= Configuration.new
86
96
  end
87
97
 
98
+ # Configure DBWatcher
99
+ #
100
+ # @yield [configuration] configuration
101
+ # @return [Configuration] configuration
88
102
  def configure
89
103
  yield(configuration)
90
104
  end