routes-analyzer 0.1.2 → 0.1.5
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.
- checksums.yaml +4 -4
- data/README.md +53 -29
- data/lib/routes/analyzer/middleware.rb +9 -11
- data/lib/routes/analyzer/route_usage_tracker.rb +31 -25
- data/lib/routes/analyzer/version.rb +1 -1
- data/lib/tasks/routes/analyzer_tasks.rake +24 -25
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3cae3e4dca655b13929955c6357b4351607dd32ae29331c643666e6d05562e32
|
|
4
|
+
data.tar.gz: 3b533755ae2bc4b810708ac9d705085f5467c3cb94714a458baa6aaa8f5eaebf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d47e92d852c70f7ce7108777532aea5f5650f562fe31607d522b178bc206ed8430f713408cb8e28989d38ff8c66c3c5f64bf4ac87407e928bff9f450b7189255
|
|
7
|
+
data.tar.gz: d3c981912a2b4894895f4f19d80842d878f6aa1edb1ec157a34e05d54e8ef67cd7c490ed055ef7d1bfd8187277e0f4d7eb923d52d127669eeaedcdcf3dea8a03
|
data/README.md
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/routes-analyzer)
|
|
4
4
|
|
|
5
|
-
A Ruby on Rails plugin that tracks and analyzes
|
|
5
|
+
A Ruby on Rails plugin that tracks and analyzes controller action usage in your application. It uses Redis to store action access patterns and provides insights into which controller actions are being used and which are not.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **
|
|
9
|
+
- **Controller Action Tracking**: Automatically tracks which controller actions are accessed and how often
|
|
10
|
+
- **Method-Agnostic Tracking**: Actions are tracked by controller#action regardless of HTTP method (GET, POST, etc.)
|
|
10
11
|
- **Redis Storage**: Uses Redis to store usage statistics efficiently
|
|
11
12
|
- **Configurable Timeframe**: Set custom analysis periods (default 30 days)
|
|
12
|
-
- **Comprehensive Reporting**: Shows both used and unused
|
|
13
|
+
- **Comprehensive Reporting**: Shows both used and unused controller actions
|
|
13
14
|
- **Rake Tasks**: Easy-to-use commands for viewing statistics and managing data
|
|
14
15
|
|
|
15
16
|
## Installation
|
|
@@ -40,10 +41,10 @@ Routes::Analyzer.configure do |config|
|
|
|
40
41
|
# Redis connection URL
|
|
41
42
|
config.redis_url = ENV['REDIS_URL'] || 'redis://localhost:6379/0'
|
|
42
43
|
|
|
43
|
-
# Redis key prefix for storing
|
|
44
|
+
# Redis key prefix for storing action usage data
|
|
44
45
|
config.redis_key_prefix = 'routes_analyzer'
|
|
45
46
|
|
|
46
|
-
# Timeframe for
|
|
47
|
+
# Timeframe for action usage analysis in days
|
|
47
48
|
config.timeframe = 30
|
|
48
49
|
end
|
|
49
50
|
```
|
|
@@ -58,38 +59,39 @@ Alternatively, you can create the configuration file manually in `config/initial
|
|
|
58
59
|
|
|
59
60
|
## Usage
|
|
60
61
|
|
|
61
|
-
Once installed and configured, the middleware will automatically start tracking
|
|
62
|
+
Once installed and configured, the middleware will automatically start tracking controller action usage. No additional code changes are required.
|
|
62
63
|
|
|
63
64
|
### Rake Tasks
|
|
64
65
|
|
|
65
|
-
#### View
|
|
66
|
+
#### View Controller Action Usage Statistics
|
|
66
67
|
|
|
67
68
|
```bash
|
|
68
69
|
bundle exec rake routes:analyzer:usage
|
|
69
70
|
```
|
|
70
71
|
|
|
71
72
|
This command shows:
|
|
72
|
-
- All
|
|
73
|
-
- Usage count for each
|
|
73
|
+
- All controller actions available in your application
|
|
74
|
+
- Usage count for each action in the specified timeframe
|
|
74
75
|
- Last access timestamp
|
|
75
|
-
- Summary statistics (total, used, unused
|
|
76
|
+
- Summary statistics (total, used, unused actions)
|
|
76
77
|
|
|
77
78
|
Example output:
|
|
78
79
|
```
|
|
79
|
-
|
|
80
|
+
Controller Action Usage Analysis (30 days)
|
|
80
81
|
================================================================================
|
|
81
82
|
|
|
82
|
-
COUNT
|
|
83
|
+
COUNT CONTROLLER#ACTION METHOD LAST ACCESSED
|
|
83
84
|
--------------------------------------------------------------------------------
|
|
84
|
-
45
|
|
85
|
-
23
|
|
86
|
-
12
|
|
87
|
-
|
|
88
|
-
0 /
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
45 users#index GET 2025-06-18 14:30
|
|
86
|
+
23 users#show GET 2025-06-18 12:15
|
|
87
|
+
12 posts#index GET 2025-06-17 09:45
|
|
88
|
+
5 posts#create POST 2025-06-16 16:20
|
|
89
|
+
0 admin/reports#index GET Never
|
|
90
|
+
0 api/v1/health#check GET Never
|
|
91
|
+
|
|
92
|
+
Total actions: 6
|
|
93
|
+
Used actions: 4
|
|
94
|
+
Unused actions: 2
|
|
93
95
|
```
|
|
94
96
|
|
|
95
97
|
#### Clear Usage Data
|
|
@@ -98,7 +100,7 @@ Unused routes: 2
|
|
|
98
100
|
bundle exec rake routes:analyzer:clear
|
|
99
101
|
```
|
|
100
102
|
|
|
101
|
-
Removes all stored
|
|
103
|
+
Removes all stored controller action usage data from Redis.
|
|
102
104
|
|
|
103
105
|
#### Check Configuration
|
|
104
106
|
|
|
@@ -111,13 +113,13 @@ Displays current configuration and tests Redis connectivity.
|
|
|
111
113
|
## How It Works
|
|
112
114
|
|
|
113
115
|
1. **Middleware Integration**: The plugin automatically adds middleware to your Rails application
|
|
114
|
-
2. **
|
|
116
|
+
2. **Controller Action Filtering**: Only controller actions from defined routes in `routes.rb` are tracked. This ensures that:
|
|
115
117
|
- Undefined routes (404 errors) are not tracked
|
|
116
118
|
- Catch-all routes that handle unknown paths don't pollute the data
|
|
117
|
-
- Only legitimate application
|
|
118
|
-
3. **Request Tracking**: Each valid HTTP request is analyzed to extract
|
|
119
|
+
- Only legitimate application controller actions are analyzed
|
|
120
|
+
3. **Request Tracking**: Each valid HTTP request is analyzed to extract controller and action information
|
|
119
121
|
4. **Redis Storage**: Usage data is stored in Redis with the following structure:
|
|
120
|
-
-
|
|
122
|
+
- Controller name and action name
|
|
121
123
|
- Access count within the timeframe
|
|
122
124
|
- Last access timestamp
|
|
123
125
|
5. **Data Expiration**: Redis keys automatically expire after the configured timeframe plus a buffer period
|
|
@@ -132,14 +134,36 @@ The middleware uses Rails' routing system to determine if a route is valid:
|
|
|
132
134
|
|
|
133
135
|
This approach ensures that only routes you've intentionally defined are included in the usage analysis.
|
|
134
136
|
|
|
137
|
+
## Controller Action Tracking
|
|
138
|
+
|
|
139
|
+
The gem tracks usage by controller and action rather than by specific URL paths. This provides more meaningful insights into which parts of your application are being used:
|
|
140
|
+
|
|
141
|
+
- **Route Definition**: `resources :users` in `routes.rb`
|
|
142
|
+
- **Actual Requests**: `GET /users/123`, `GET /users/456`, `POST /users/789/update`
|
|
143
|
+
- **Tracked As**: Separate entries for `users#show` and `users#update` actions
|
|
144
|
+
|
|
145
|
+
This means that accessing different user IDs (e.g., `/users/123`, `/users/456`, `/users/789`) will all be counted under the same `users#show` action, giving you a clear picture of which controller actions are being utilized.
|
|
146
|
+
|
|
147
|
+
**Example Output:**
|
|
148
|
+
```
|
|
149
|
+
COUNT CONTROLLER#ACTION METHOD LAST ACCESSED
|
|
150
|
+
--------------------------------------------------------------
|
|
151
|
+
15 users#show GET 2025-06-18 14:30
|
|
152
|
+
8 users#update PATCH 2025-06-18 12:15
|
|
153
|
+
23 posts#show GET 2025-06-17 09:45
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
This grouping provides much more meaningful insights into which parts of your application are being used.
|
|
157
|
+
|
|
135
158
|
## Data Structure
|
|
136
159
|
|
|
137
|
-
For each tracked
|
|
160
|
+
For each tracked controller action, the following data is stored in Redis:
|
|
138
161
|
|
|
139
162
|
```ruby
|
|
140
163
|
{
|
|
141
|
-
|
|
142
|
-
|
|
164
|
+
controller: "users", # Controller name
|
|
165
|
+
action: "show", # Action name
|
|
166
|
+
method: "GET", # HTTP method (for reference)
|
|
143
167
|
count: 15, # Number of accesses
|
|
144
168
|
last_accessed: 1718721600 # Unix timestamp
|
|
145
169
|
}
|
|
@@ -38,7 +38,7 @@ module Routes
|
|
|
38
38
|
|
|
39
39
|
return unless route_info
|
|
40
40
|
|
|
41
|
-
redis_key = build_redis_key(route_info[:
|
|
41
|
+
redis_key = build_redis_key(route_info[:controller], route_info[:action])
|
|
42
42
|
current_time = Time.current
|
|
43
43
|
|
|
44
44
|
configuration.redis_client.multi do |redis|
|
|
@@ -48,8 +48,9 @@ module Routes
|
|
|
48
48
|
# Update last accessed timestamp
|
|
49
49
|
redis.hset(redis_key, "last_accessed", current_time.to_i)
|
|
50
50
|
|
|
51
|
-
# Set
|
|
52
|
-
redis.hset(redis_key, "
|
|
51
|
+
# Set controller#action and method info (in case it's the first time)
|
|
52
|
+
redis.hset(redis_key, "controller", route_info[:controller])
|
|
53
|
+
redis.hset(redis_key, "action", route_info[:action])
|
|
53
54
|
redis.hset(redis_key, "method", route_info[:method])
|
|
54
55
|
|
|
55
56
|
# Set expiration based on timeframe (add some buffer)
|
|
@@ -92,15 +93,12 @@ module Routes
|
|
|
92
93
|
controller = env["action_controller.instance"]
|
|
93
94
|
action = controller.action_name
|
|
94
95
|
controller_name = controller.controller_name
|
|
95
|
-
|
|
96
|
-
# Build route pattern from request path, removing query parameters
|
|
97
|
-
route_path = request.path_info
|
|
96
|
+
method = request.request_method.upcase
|
|
98
97
|
|
|
99
98
|
{
|
|
100
|
-
route: route_path,
|
|
101
|
-
method: request.request_method.upcase,
|
|
102
99
|
controller: controller_name,
|
|
103
|
-
action: action
|
|
100
|
+
action: action,
|
|
101
|
+
method: method
|
|
104
102
|
}
|
|
105
103
|
end
|
|
106
104
|
rescue => e
|
|
@@ -108,8 +106,8 @@ module Routes
|
|
|
108
106
|
nil
|
|
109
107
|
end
|
|
110
108
|
|
|
111
|
-
def build_redis_key(
|
|
112
|
-
"#{configuration.redis_key_prefix}:#{
|
|
109
|
+
def build_redis_key(controller, action)
|
|
110
|
+
"#{configuration.redis_key_prefix}:#{controller}##{action}"
|
|
113
111
|
end
|
|
114
112
|
|
|
115
113
|
def configuration
|
|
@@ -9,7 +9,7 @@ module Routes
|
|
|
9
9
|
def get_usage_stats
|
|
10
10
|
cutoff_time = @configuration.timeframe.days.ago.to_i
|
|
11
11
|
|
|
12
|
-
# Get all
|
|
12
|
+
# Get all controller#action keys
|
|
13
13
|
pattern = "#{@configuration.redis_key_prefix}:*"
|
|
14
14
|
keys = @redis.keys(pattern)
|
|
15
15
|
|
|
@@ -24,7 +24,8 @@ module Routes
|
|
|
24
24
|
next if last_accessed && last_accessed < cutoff_time
|
|
25
25
|
|
|
26
26
|
usage_stats << {
|
|
27
|
-
|
|
27
|
+
controller: route_data["controller"],
|
|
28
|
+
action: route_data["action"],
|
|
28
29
|
method: route_data["method"],
|
|
29
30
|
count: route_data["count"]&.to_i || 0,
|
|
30
31
|
last_accessed: last_accessed ? Time.at(last_accessed) : nil
|
|
@@ -35,54 +36,59 @@ module Routes
|
|
|
35
36
|
usage_stats.sort_by { |stat| -stat[:count] }
|
|
36
37
|
end
|
|
37
38
|
|
|
38
|
-
def
|
|
39
|
+
def get_all_defined_actions
|
|
39
40
|
return [] unless defined?(Rails) && Rails.application
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
actions = []
|
|
43
|
+
Rails.application.routes.routes.each do |route|
|
|
44
|
+
next unless route.defaults[:controller] && route.defaults[:action]
|
|
45
|
+
|
|
46
|
+
actions << {
|
|
45
47
|
controller: route.defaults[:controller],
|
|
46
48
|
action: route.defaults[:action],
|
|
49
|
+
method: route.verb,
|
|
50
|
+
route: route.path.spec.to_s.gsub(/\(\.:format\)$/, ""),
|
|
47
51
|
name: route.name
|
|
48
52
|
}
|
|
49
|
-
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Remove duplicates based on controller#action (not method-specific)
|
|
56
|
+
actions.uniq { |a| "#{a[:controller]}##{a[:action]}" }
|
|
50
57
|
end
|
|
51
58
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
def merge_with_defined_actions(usage_stats)
|
|
60
|
+
defined_actions = get_all_defined_actions
|
|
61
|
+
usage_by_action = usage_stats.index_by { |stat| "#{stat[:controller]}##{stat[:action]}" }
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
merged_actions = []
|
|
57
64
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
usage_stat =
|
|
65
|
+
defined_actions.each do |defined_action|
|
|
66
|
+
action_key = "#{defined_action[:controller]}##{defined_action[:action]}"
|
|
67
|
+
usage_stat = usage_by_action[action_key]
|
|
61
68
|
|
|
62
69
|
if usage_stat
|
|
63
|
-
|
|
70
|
+
merged_actions << defined_action.merge(usage_stat)
|
|
64
71
|
else
|
|
65
|
-
|
|
72
|
+
merged_actions << defined_action.merge(
|
|
66
73
|
count: 0,
|
|
67
74
|
last_accessed: nil
|
|
68
75
|
)
|
|
69
76
|
end
|
|
70
77
|
end
|
|
71
78
|
|
|
72
|
-
# Add any tracked
|
|
79
|
+
# Add any tracked actions that aren't in the defined actions (in case of dynamic actions)
|
|
73
80
|
usage_stats.each do |usage_stat|
|
|
74
|
-
|
|
75
|
-
unless
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
action: nil,
|
|
81
|
+
action_key = "#{usage_stat[:controller]}##{usage_stat[:action]}"
|
|
82
|
+
unless defined_actions.any? { |da| "#{da[:controller]}##{da[:action]}" == action_key }
|
|
83
|
+
merged_actions << usage_stat.merge(
|
|
84
|
+
route: nil,
|
|
79
85
|
name: nil
|
|
80
86
|
)
|
|
81
87
|
end
|
|
82
88
|
end
|
|
83
89
|
|
|
84
|
-
# Sort by count (descending), then by
|
|
85
|
-
|
|
90
|
+
# Sort by count (descending), then by controller#action name
|
|
91
|
+
merged_actions.sort_by { |action| [ -action[:count], "#{action[:controller]}##{action[:action]}" ] }
|
|
86
92
|
end
|
|
87
93
|
end
|
|
88
94
|
end
|
|
@@ -1,60 +1,59 @@
|
|
|
1
1
|
namespace :routes do
|
|
2
2
|
namespace :analyzer do
|
|
3
|
-
desc "Show
|
|
3
|
+
desc "Show controller action usage statistics"
|
|
4
4
|
task usage: :environment do
|
|
5
5
|
begin
|
|
6
6
|
config = Routes::Analyzer.configuration
|
|
7
7
|
tracker = Routes::Analyzer::RouteUsageTracker.new(config)
|
|
8
8
|
|
|
9
|
-
puts "
|
|
9
|
+
puts "Controller Action Usage Analysis (#{config.timeframe} days)"
|
|
10
10
|
puts "=" * 80
|
|
11
11
|
puts
|
|
12
12
|
|
|
13
13
|
if config.valid?
|
|
14
14
|
usage_stats = tracker.get_usage_stats
|
|
15
|
-
|
|
15
|
+
merged_actions = tracker.merge_with_defined_actions(usage_stats)
|
|
16
16
|
else
|
|
17
|
-
puts "Warning: Redis not configured. Showing defined
|
|
17
|
+
puts "Warning: Redis not configured. Showing defined actions only."
|
|
18
18
|
puts
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
merged_actions = tracker.get_all_defined_actions.map do |action|
|
|
20
|
+
action.merge(count: 0, last_accessed: nil)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
if
|
|
25
|
-
puts "No
|
|
24
|
+
if merged_actions.empty?
|
|
25
|
+
puts "No controller actions found."
|
|
26
26
|
next
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
# Print header
|
|
30
|
-
printf "%-8s %-40s %-15s
|
|
30
|
+
printf "%-8s %-40s %-15s %s\n", "COUNT", "CONTROLLER#ACTION", "METHOD", "LAST ACCESSED"
|
|
31
31
|
puts "-" * 80
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
controller_action = if
|
|
35
|
-
"#{
|
|
33
|
+
merged_actions.each do |action|
|
|
34
|
+
controller_action = if action[:controller] && action[:action]
|
|
35
|
+
"#{action[:controller]}##{action[:action]}"
|
|
36
36
|
else
|
|
37
37
|
"N/A"
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
last_accessed = if
|
|
41
|
-
|
|
40
|
+
last_accessed = if action[:last_accessed]
|
|
41
|
+
action[:last_accessed].strftime("%Y-%m-%d %H:%M")
|
|
42
42
|
else
|
|
43
43
|
"Never"
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
printf "%-8d %-40s %-15s
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
controller_action[0, 20],
|
|
46
|
+
printf "%-8d %-40s %-15s %s\n",
|
|
47
|
+
action[:count],
|
|
48
|
+
controller_action[0, 40],
|
|
49
|
+
action[:method],
|
|
51
50
|
last_accessed
|
|
52
51
|
end
|
|
53
52
|
|
|
54
53
|
puts
|
|
55
|
-
puts "Total
|
|
56
|
-
puts "Used
|
|
57
|
-
puts "Unused
|
|
54
|
+
puts "Total actions: #{merged_actions.count}"
|
|
55
|
+
puts "Used actions: #{merged_actions.count { |a| a[:count] > 0 }}"
|
|
56
|
+
puts "Unused actions: #{merged_actions.count { |a| a[:count] == 0 }}"
|
|
58
57
|
|
|
59
58
|
rescue => e
|
|
60
59
|
puts "Error: #{e.message}"
|
|
@@ -69,7 +68,7 @@ namespace :routes do
|
|
|
69
68
|
end
|
|
70
69
|
end
|
|
71
70
|
|
|
72
|
-
desc "Clear all
|
|
71
|
+
desc "Clear all controller action usage data"
|
|
73
72
|
task clear: :environment do
|
|
74
73
|
begin
|
|
75
74
|
config = Routes::Analyzer.configuration
|
|
@@ -79,9 +78,9 @@ namespace :routes do
|
|
|
79
78
|
|
|
80
79
|
if keys.any?
|
|
81
80
|
redis.del(*keys)
|
|
82
|
-
puts "Cleared #{keys.count}
|
|
81
|
+
puts "Cleared #{keys.count} controller action usage records."
|
|
83
82
|
else
|
|
84
|
-
puts "No
|
|
83
|
+
puts "No controller action usage data found to clear."
|
|
85
84
|
end
|
|
86
85
|
|
|
87
86
|
rescue => e
|