rails_server_analytics 0.1.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.
- checksums.yaml +7 -0
- data/lib/generators/analytics_generator.rb +31 -0
- data/lib/generators/templates/analytic.rb +37 -0
- data/lib/generators/templates/analytics_controller.rb +59 -0
- data/lib/generators/templates/index.html.erb.tt +212 -0
- data/lib/generators/templates/migration.rb +9 -0
- data/lib/rails_server_analytics/analytics_tracer.rb +67 -0
- data/lib/rails_server_analytics/engine.rb +13 -0
- data/lib/rails_server_analytics/version.rb +5 -0
- data/lib/rails_server_analytics.rb +9 -0
- metadata +55 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ceecf3d520444f52ee03e83e013c11c051060c8f7aa68a6f51daade9f4ac7d32
|
4
|
+
data.tar.gz: aba1fbf7819d6b292a9900ee29cd1d162b96057aa4d88ccf3bbfe4c9aed13252
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0663736f3c8feb23bdae41ec480b2342a521e183deae4da9e4de7d6d8bcea711485cafb5c55e8f48e3d81e04c21f8ebe310fc46625b9341149832dfa05365958
|
7
|
+
data.tar.gz: '092920b9405f6d39516a267c6001d33aed1c93b598e3c7908a7d9a41baaa34db0ebc2e2356fbcd1a5365285e0be546c190b4f20f6cdca128f2c679cd83903749'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "rails/generators/base"
|
2
|
+
|
3
|
+
module Analytics
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
7
|
+
desc "analyctics controller installation generator"
|
8
|
+
|
9
|
+
def create_analytics_controller
|
10
|
+
template "analytics_controller.rb", "app/controllers/analytics_controller.rb"
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_analytics_view
|
14
|
+
template "index.html.erb.tt", "app/views/analytics/index.html.erb"
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_analytics_route
|
18
|
+
route "get 'analytics', to: 'analytics#index'"
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_model_file
|
22
|
+
template "analytic.rb", "app/models/analytic.rb"
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_migration_file
|
26
|
+
timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
27
|
+
template "migration.rb", "db/migrate/#{timestamp}_create_analytics.rb"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# analytics_data = {
|
2
|
+
# status: ok
|
3
|
+
# headers: {
|
4
|
+
#
|
5
|
+
# },
|
6
|
+
# request_uri: "/posts",
|
7
|
+
# request_method: "get",
|
8
|
+
# path_info "/posts",
|
9
|
+
# ip,
|
10
|
+
# host: "127.0.0.1",
|
11
|
+
# time: Time.now,
|
12
|
+
# time_spent: Time.now
|
13
|
+
# }
|
14
|
+
|
15
|
+
class Analytic < ApplicationRecord
|
16
|
+
def content
|
17
|
+
self.analytics_data = JSON.parse(analytics_data)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.user
|
21
|
+
Rails.root.to_path.split("/")[2]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.root
|
25
|
+
Rails.root.to_path
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.version
|
29
|
+
ruby_version = `ruby -v`.split(" ")[1]
|
30
|
+
rails_version = `rails -v`.split(" ")[1]
|
31
|
+
|
32
|
+
{
|
33
|
+
ruby_v: ruby_version,
|
34
|
+
rails_v: rails_version
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class AnalyticsController < ApplicationController
|
2
|
+
before_action :get_analytics, :process_raw_analytics
|
3
|
+
|
4
|
+
def index
|
5
|
+
respond_to do |format|
|
6
|
+
format.html { render :index }
|
7
|
+
format.json { render json: @analytics_json }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def get_analytics
|
14
|
+
@analytics = Analytic.all
|
15
|
+
@analytics.each(&:content)
|
16
|
+
end
|
17
|
+
|
18
|
+
def process_raw_analytics
|
19
|
+
request_count = @analytics.count
|
20
|
+
total_request_per_day = @analytics.where(updated_at: (Time.now - 1.day)..Time.now).pluck(:updated_at).count
|
21
|
+
|
22
|
+
average_response_time = get_average("time_spent")
|
23
|
+
average_cpu_usage = get_average("cpu_usage")
|
24
|
+
|
25
|
+
most_frequent_request = get_most_frequent("request_uri")
|
26
|
+
most_frequent_request_method = get_most_frequent("request_method")
|
27
|
+
|
28
|
+
version = Analytic.version
|
29
|
+
user = Analytic.user
|
30
|
+
root = Analytic.root
|
31
|
+
|
32
|
+
@analytics_json = {
|
33
|
+
analytics: @analytics,
|
34
|
+
request_count: request_count,
|
35
|
+
total_request_per_day: total_request_per_day,
|
36
|
+
average_response_time: average_response_time,
|
37
|
+
most_frequent_request: most_frequent_request,
|
38
|
+
most_frequent_request_method: most_frequent_request_method,
|
39
|
+
average_cpu_usage: average_cpu_usage,
|
40
|
+
ruby_v: version[:ruby_v],
|
41
|
+
rails_v: version[:rails_v],
|
42
|
+
user: user,
|
43
|
+
root: root
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_most_frequent(data)
|
48
|
+
all_request = @analytics.map(&:analytics_data).pluck(data)
|
49
|
+
all_request.detect { |r| all_request.count(r) > 1 }
|
50
|
+
|
51
|
+
counts = all_request.group_by { |e| e }.transform_values(&:size)
|
52
|
+
counts.max_by { |_, count| count }[0]
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_average(data)
|
56
|
+
all_data = @analytics.map(&:analytics_data).pluck(data)
|
57
|
+
(all_data.sum / @analytics.count).truncate(3)
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
<!-- analytics_dashboard.html.erb -->
|
2
|
+
<title>System Analytics Dashboard</title>
|
3
|
+
<style>
|
4
|
+
/* Reset and base styles */
|
5
|
+
* {
|
6
|
+
margin: 0;
|
7
|
+
padding: 0;
|
8
|
+
box-sizing: border-box;
|
9
|
+
}
|
10
|
+
|
11
|
+
body {
|
12
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
13
|
+
line-height: 1.6;
|
14
|
+
color: #333;
|
15
|
+
background-color: #f5f5f5;
|
16
|
+
padding: 20px;
|
17
|
+
}
|
18
|
+
|
19
|
+
/* Container */
|
20
|
+
.container {
|
21
|
+
max-width: 1200px;
|
22
|
+
margin: 0 auto;
|
23
|
+
}
|
24
|
+
|
25
|
+
/* Header styles */
|
26
|
+
.header {
|
27
|
+
display: flex;
|
28
|
+
justify-content: space-between;
|
29
|
+
align-items: center;
|
30
|
+
margin-bottom: 30px;
|
31
|
+
background-color: white;
|
32
|
+
padding: 20px;
|
33
|
+
border-radius: 8px;
|
34
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
35
|
+
}
|
36
|
+
|
37
|
+
.version-info {
|
38
|
+
display: flex;
|
39
|
+
gap: 20px;
|
40
|
+
font-size: 0.9em;
|
41
|
+
color: #666;
|
42
|
+
}
|
43
|
+
|
44
|
+
/* Grid layout */
|
45
|
+
.metrics-grid {
|
46
|
+
display: grid;
|
47
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
48
|
+
gap: 20px;
|
49
|
+
margin-bottom: 30px;
|
50
|
+
}
|
51
|
+
|
52
|
+
/* Card styles */
|
53
|
+
.card {
|
54
|
+
background: white;
|
55
|
+
padding: 20px;
|
56
|
+
border-radius: 8px;
|
57
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
58
|
+
}
|
59
|
+
|
60
|
+
.metric-card {
|
61
|
+
text-align: center;
|
62
|
+
}
|
63
|
+
|
64
|
+
.metric-title {
|
65
|
+
font-size: 0.9em;
|
66
|
+
color: #666;
|
67
|
+
margin-bottom: 10px;
|
68
|
+
}
|
69
|
+
|
70
|
+
.metric-value {
|
71
|
+
font-size: 1.8em;
|
72
|
+
font-weight: bold;
|
73
|
+
color: #2563eb;
|
74
|
+
}
|
75
|
+
|
76
|
+
/* Chart styles */
|
77
|
+
.chart-container {
|
78
|
+
margin-bottom: 30px;
|
79
|
+
}
|
80
|
+
|
81
|
+
.chart {
|
82
|
+
width: 100%;
|
83
|
+
height: 300px;
|
84
|
+
background: white;
|
85
|
+
padding: 20px;
|
86
|
+
border-radius: 8px;
|
87
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
88
|
+
}
|
89
|
+
|
90
|
+
/* Request details styles */
|
91
|
+
.details-grid {
|
92
|
+
display: grid;
|
93
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
94
|
+
gap: 20px;
|
95
|
+
}
|
96
|
+
|
97
|
+
.detail-item {
|
98
|
+
margin-bottom: 15px;
|
99
|
+
}
|
100
|
+
|
101
|
+
.detail-label {
|
102
|
+
font-size: 0.9em;
|
103
|
+
color: #666;
|
104
|
+
margin-bottom: 5px;
|
105
|
+
}
|
106
|
+
|
107
|
+
.detail-value {
|
108
|
+
font-weight: 600;
|
109
|
+
}
|
110
|
+
|
111
|
+
/* Responsive adjustments */
|
112
|
+
@media (max-width: 768px) {
|
113
|
+
.header {
|
114
|
+
flex-direction: column;
|
115
|
+
gap: 10px;
|
116
|
+
text-align: center;
|
117
|
+
}
|
118
|
+
|
119
|
+
.version-info {
|
120
|
+
flex-direction: column;
|
121
|
+
gap: 5px;
|
122
|
+
}
|
123
|
+
}
|
124
|
+
</style>
|
125
|
+
<div class="container">
|
126
|
+
<!-- Header -->
|
127
|
+
<div class="header">
|
128
|
+
<h1>System Analytics Dashboard</h1>
|
129
|
+
<div class="version-info">
|
130
|
+
<span>Ruby <%%= @analytics_json[:ruby_v] %></span>
|
131
|
+
<span>Rails <%%= @analytics_json[:rails_v] %></span>
|
132
|
+
</div>
|
133
|
+
</div>
|
134
|
+
|
135
|
+
<!-- Key Metrics -->
|
136
|
+
<div class="metrics-grid">
|
137
|
+
<div class="card metric-card">
|
138
|
+
<div class="metric-title">Total Requests</div>
|
139
|
+
<div class="metric-value"><%%= number_with_delimiter(@analytics_json[:request_count]) %></div>
|
140
|
+
</div>
|
141
|
+
|
142
|
+
<div class="card metric-card">
|
143
|
+
<div class="metric-title">Avg Response Time</div>
|
144
|
+
<div class="metric-value"><%%= @analytics_json[:average_response_time] %>ms</div>
|
145
|
+
</div>
|
146
|
+
|
147
|
+
<div class="card metric-card">
|
148
|
+
<div class="metric-title">CPU Usage</div>
|
149
|
+
<div class="metric-value"><%%= @analytics_json[:average_cpu_usage] %>%</div>
|
150
|
+
</div>
|
151
|
+
|
152
|
+
<div class="card metric-card">
|
153
|
+
<div class="metric-title">System User</div>
|
154
|
+
<div class="metric-value"><%%= @analytics_json[:user] %></div>
|
155
|
+
</div>
|
156
|
+
</div>
|
157
|
+
|
158
|
+
<!-- Chart Card -->
|
159
|
+
<div class="chart-container card">
|
160
|
+
<h2>Requests per Day</h2>
|
161
|
+
<div class="chart">
|
162
|
+
<%%
|
163
|
+
# You would need to implement chart rendering here
|
164
|
+
# You could use Chart.js or any other JavaScript charting library
|
165
|
+
# For now, we'll show the data in a table format
|
166
|
+
%>
|
167
|
+
<table style="width: 100%; border-collapse: collapse;">
|
168
|
+
<thead>
|
169
|
+
<tr>
|
170
|
+
<th style="text-align: left; padding: 8px; border-bottom: 1px solid #ddd;">Date</th>
|
171
|
+
<th style="text-align: right; padding: 8px; border-bottom: 1px solid #ddd;">Requests</th>
|
172
|
+
</tr>
|
173
|
+
</thead>
|
174
|
+
<tbody>
|
175
|
+
<tr>
|
176
|
+
<td><%% @analytics_json[:total_request_per_day] %></td>
|
177
|
+
</tr>
|
178
|
+
</tbody>
|
179
|
+
</table>
|
180
|
+
</div>
|
181
|
+
</div>
|
182
|
+
|
183
|
+
<!-- Request Details -->
|
184
|
+
<div class="card">
|
185
|
+
<h2 style="margin-bottom: 20px;">Request Details</h2>
|
186
|
+
<div class="details-grid">
|
187
|
+
<div class="detail-item">
|
188
|
+
<div class="detail-label">Most Frequent Request</div>
|
189
|
+
<div class="detail-value"><%%= @analytics_json[:most_frequent_request] %></div>
|
190
|
+
</div>
|
191
|
+
|
192
|
+
<div class="detail-item">
|
193
|
+
<div class="detail-label">Request Method</div>
|
194
|
+
<div class="detail-value"><%%= @analytics_json[:most_frequent_request_method] %></div>
|
195
|
+
</div>
|
196
|
+
|
197
|
+
<div class="detail-item">
|
198
|
+
<div class="detail-label">System Root</div>
|
199
|
+
<div class="detail-value"><%%= @analytics_json[:root] %></div>
|
200
|
+
</div>
|
201
|
+
</div>
|
202
|
+
</div>
|
203
|
+
</div>
|
204
|
+
|
205
|
+
<%% if @analytics_json[:auto_refresh] %>
|
206
|
+
<script>
|
207
|
+
setTimeout(function() {
|
208
|
+
window.location.reload();
|
209
|
+
}, 5000);
|
210
|
+
</script>
|
211
|
+
<%% end %>
|
212
|
+
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "active_support/log_subscriber"
|
2
|
+
|
3
|
+
module RailsServerAnalytics
|
4
|
+
class AnalyticsTracer < ActiveSupport::LogSubscriber
|
5
|
+
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
Rails.logger.info(color("Processing request in middleware", YELLOW))
|
12
|
+
|
13
|
+
time_start = Time.now
|
14
|
+
before_cpu = `ps -o %cpu= -p #{Process.pid}`.strip.to_f
|
15
|
+
|
16
|
+
status, headers, response = @app.call(env)
|
17
|
+
# request = ActionDispatch::Request.new env
|
18
|
+
|
19
|
+
# data = {status:, headers:, response: response.body, request: request.body, time:}
|
20
|
+
request_uri = env["REQUEST_URI"]
|
21
|
+
request_method = env["REQUEST_METHOD"].downcase
|
22
|
+
path_info = env["PATH_INFO"]
|
23
|
+
ip = env["action_dispatch.remote_ip"].calculate_ip
|
24
|
+
host = env["HTTP_HOST"]
|
25
|
+
time_end = Time.now
|
26
|
+
|
27
|
+
time_spent = time_end - time_start
|
28
|
+
|
29
|
+
after_cpu = `ps -o %cpu= -p #{Process.pid}`.strip.to_f
|
30
|
+
cpu_usage = after_cpu - before_cpu
|
31
|
+
|
32
|
+
return [status, headers, response] if path_info.eql? "/analytics"
|
33
|
+
|
34
|
+
analytics_data = JSON.pretty_generate({
|
35
|
+
status:,
|
36
|
+
headers:,
|
37
|
+
request_uri:,
|
38
|
+
request_method:,
|
39
|
+
path_info:,
|
40
|
+
ip:,
|
41
|
+
host:,
|
42
|
+
time_spent:,
|
43
|
+
cpu_usage:,
|
44
|
+
})
|
45
|
+
|
46
|
+
analytics = Analytic.create(analytics_data:)
|
47
|
+
|
48
|
+
Rails.logger.info(color("response data stored", GREEN))
|
49
|
+
# write_data(data)
|
50
|
+
|
51
|
+
[status, headers, response]
|
52
|
+
end
|
53
|
+
|
54
|
+
def write_data(data)
|
55
|
+
json_path =
|
56
|
+
if defined?(Rails)
|
57
|
+
analytics_dir = File.join(Rails.root, "analytics_data/")
|
58
|
+
FileUtils.mkdir_p(analytics_dir)[0] + "trace.json"
|
59
|
+
end
|
60
|
+
|
61
|
+
File.open(json_path, "w") do |f|
|
62
|
+
f.write(JSON.pretty_generate(data))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "rails/engine"
|
2
|
+
|
3
|
+
module RailsServerAnalytics
|
4
|
+
class Engine < Rails::Engine
|
5
|
+
initializer "server_analytics.configure_middleware" do |app|
|
6
|
+
app.middleware.use RailsServerAnalytics::AnalyticsTracer
|
7
|
+
end
|
8
|
+
|
9
|
+
generators do
|
10
|
+
require "generators/analytics_generator"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "rails_server_analytics/version"
|
4
|
+
require "rails_server_analytics/analytics_tracer"
|
5
|
+
require "rails_server_analytics/engine" if defined?(Rails::Engine)
|
6
|
+
|
7
|
+
module RailsServerAnalytics
|
8
|
+
class Error < StandardError; end
|
9
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails_server_analytics
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chong Wei Jie
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-11-10 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: a gem that applies a custom middleware in a rails application for tracing
|
14
|
+
requests and generates an analytics page for monitoring.
|
15
|
+
email:
|
16
|
+
- jackchong398@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/generators/analytics_generator.rb
|
22
|
+
- lib/generators/templates/analytic.rb
|
23
|
+
- lib/generators/templates/analytics_controller.rb
|
24
|
+
- lib/generators/templates/index.html.erb.tt
|
25
|
+
- lib/generators/templates/migration.rb
|
26
|
+
- lib/rails_server_analytics.rb
|
27
|
+
- lib/rails_server_analytics/analytics_tracer.rb
|
28
|
+
- lib/rails_server_analytics/engine.rb
|
29
|
+
- lib/rails_server_analytics/version.rb
|
30
|
+
homepage: https://github.com/Cwjiee/rails-server-analytics
|
31
|
+
licenses:
|
32
|
+
- MIT
|
33
|
+
metadata:
|
34
|
+
homepage_uri: https://github.com/Cwjiee/rails-server-analytics
|
35
|
+
source_code_uri: https://github.com/Cwjiee/rails-server-analytics
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 3.0.0
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubygems_version: 3.5.11
|
52
|
+
signing_key:
|
53
|
+
specification_version: 4
|
54
|
+
summary: A middleware to trace and montior requests and responses in rails
|
55
|
+
test_files: []
|