-
Notifications
You must be signed in to change notification settings - Fork 4
/
allgood.rb
216 lines (161 loc) · 8.34 KB
/
allgood.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
require 'open-uri'
TEST_IMAGE = URI.open("https://picsum.photos/id/237/536/354").read
# --- ACTIVE RECORD ---
check "We have an active database connection" do
make_sure ActiveRecord::Base.connection.connect!.active?
end
check "The database can perform a simple query" do
make_sure ActiveRecord::Base.connection.execute("SELECT 1 LIMIT 1").any?
end
check "The database can perform writes" do
table_name = "allgood_health_check_#{Time.now.to_i}"
random_id = rand(1..999999)
result = ActiveRecord::Base.connection.execute(<<~SQL)
DROP TABLE IF EXISTS #{table_name};
CREATE TEMPORARY TABLE #{table_name} (id integer);
INSERT INTO #{table_name} (id) VALUES (#{random_id});
SELECT id FROM #{table_name} LIMIT 1;
SQL
ActiveRecord::Base.connection.execute("DROP TABLE #{table_name}")
make_sure result.present? && result.first["id"] == random_id, "Able to write to temporary table"
end
check "The database connection pool is healthy" do
pool = ActiveRecord::Base.connection_pool
used_connections = pool.connections.count
max_connections = pool.size
usage_percentage = (used_connections.to_f / max_connections * 100).round
make_sure usage_percentage < 90, "Pool usage at #{usage_percentage}% (#{used_connections}/#{max_connections})"
end
check "Database migrations are up to date" do
make_sure ActiveRecord::Migration.check_all_pending! == nil
end
# --- IMAGE PROCESSING ---
check "Vips (libvips) is installed on Linux", except: :development do
output = `ldconfig -p | grep libvips`
make_sure output.present? && output.include?("libvips.so") && output.include?("libvips-cpp.so"), "libvips is found in the Linux system's library cache"
end
check "Vips is available to Rails" do
throw "ImageProcessing::Vips is not available" if !ImageProcessing::Vips.present? # Need this line to load `Vips`
make_sure Vips::VERSION.present?, "Vips available with version #{Vips::VERSION}"
end
check "Vips can perform operations on images" do
throw "ImageProcessing::Vips is not available" if !ImageProcessing::Vips.present? # Need this line to load `Vips`
image = Vips::Image.new_from_buffer(TEST_IMAGE, "")
processed_image = image
.gaussblur(10) # Apply Gaussian blur with sigma 10
.linear([1.2], [0]) # Increase brightness
.invert # Invert colors for a wild effect
.sharpen # Apply sharpening
.resize(0.5)
make_sure processed_image.present? && processed_image.width == 268 && processed_image.height == 177, "If we input an image of 536x354px, and we apply filters and a 0.5 resize, we should get an image of 268x177px"
end
check "ImageProcessing::Vips is available to Rails" do
make_sure ImageProcessing::Vips.present?
end
check "ImageProcessing can perform operations on images" do
image_processing_image = ImageProcessing::Vips
.source(Vips::Image.new_from_buffer(TEST_IMAGE, ""))
.resize_to_limit(123, 123) # Resize to fit within 500x500
.convert("webp") # Convert to webp format
.call
processed_image = Vips::Image.new_from_file(image_processing_image.path)
make_sure processed_image.present? && processed_image.width == 123 && processed_image.get("vips-loader") == "webpload", "ImageProcessing can resize and convert to webp"
end
# --- ACTIVE STORAGE ---
check "Active Storage is available to Rails" do
make_sure ActiveStorage.present?
end
check "Active Storage tables are present in the database" do
make_sure ActiveRecord::Base.connection.table_exists?("active_storage_attachments") && ActiveRecord::Base.connection.table_exists?("active_storage_blobs")
end
check "Active Storage has a valid client configured" do
service = ActiveStorage::Blob.service
service_name = service&.class&.name&.split("::")&.last&.split("Service")&.first
if !service_name.downcase.include?("disk")
make_sure service.present? && service.respond_to?(:client) && service.client.present?, "Active Storage service has a valid #{service_name} client configured"
else
make_sure !Rails.env.production? && service.present?, "Active Storage using #{service_name} service in #{Rails.env.to_s}"
end
end
check "ActiveStorage can store images, retrieve them, and purge them" do
blob = ActiveStorage::Blob.create_and_upload!(io: StringIO.new(TEST_IMAGE), filename: "allgood-test-image-#{Time.now.to_i}.jpg", content_type: "image/jpeg")
blob_key = blob.key
make_sure blob.persisted? && blob.service.exist?(blob_key)
blob.purge
make_sure !blob.service.exist?(blob_key), "Image needs to be successfully stored, retrieved, and purged from #{ActiveStorage::Blob.service.name} (#{ActiveStorage::Blob.service.class.name})"
end
# --- CACHE ---
check "Cache is accessible and functioning" do
cache_value = "allgood_#{Time.now.to_i}"
Rails.cache.write("allgood_health_check_test", cache_value, expires_in: 1.minute)
make_sure Rails.cache.read("allgood_health_check_test") == cache_value, "The `allgood_health_check_test` key in the cache should return the string `#{cache_value}`"
end
# --- SOLID QUEUE ---
check "SolidQueue is available to Rails" do
make_sure SolidQueue.present?
end
check "We have an active SolidQueue connection to the database" do
make_sure SolidQueue::Job.connection.connect!.active?
end
check "SolidQueue tables are present in the database" do
make_sure SolidQueue::Job.connection.table_exists?("solid_queue_jobs") && SolidQueue::Job.connection.table_exists?("solid_queue_failed_executions") && SolidQueue::Job.connection.table_exists?("solid_queue_semaphores")
end
check "The percentage of failed jobs in the last 24 hours is less than 1%", only: :production do
failed_jobs = SolidQueue::FailedExecution.where(created_at: 24.hours.ago..Time.now).count
all_jobs = SolidQueue::Job.where(created_at: 24.hours.ago..Time.now).count
if all_jobs > 10
percentage = all_jobs > 0 ? (failed_jobs.to_f / all_jobs.to_f * 100) : 0
make_sure percentage < 1, "#{percentage.round(2)}% of jobs are failing"
else
make_sure true, "Not enough jobs to calculate meaningful failure rate (only #{all_jobs} jobs in last 24h)"
end
end
# --- ACTION CABLE ---
check "ActionCable is configured and running" do
make_sure ActionCable.server.present?, "ActionCable server should be running"
end
check "ActionCable is configured to accept connections with a valid adapter" do
make_sure ActionCable.server.config.allow_same_origin_as_host, "ActionCable server should be configured to accept connections"
adapter = ActionCable.server.config.cable["adapter"]
if Rails.env.production?
make_sure adapter.in?(["solid_cable", "redis"]), "ActionCable running #{adapter} adapter in #{Rails.env.to_s}"
else
make_sure adapter.in?(["solid_cable", "async"]), "ActionCable running #{adapter} adapter in #{Rails.env.to_s}"
end
end
check "ActionCable can broadcast messages and store them in SolidCable" do
test_message = "allgood_test_#{Time.now.to_i}"
begin
ActionCable.server.broadcast("allgood_test_channel", { message: test_message })
# Verify message was stored in SolidCable
message = SolidCable::Message.where(channel: "allgood_test_channel")
.order(created_at: :desc)
.first
make_sure message.present?, "Message should be stored in SolidCable"
make_sure message.payload.include?(test_message) && message.destroy, "Message payload should contain our test message"
rescue => e
make_sure false, "Failed to broadcast/verify message: #{e.message}"
end
end
# --- SYSTEM ---
check "Disk space usage is below 90%", only: :production do
usage = `df -h / | tail -1 | awk '{print $5}' | sed 's/%//'`.to_i
expect(usage).to_be_less_than(90)
end
check "Memory usage is below 90%", only: :production do
usage = `free | grep Mem | awk '{print $3/$2 * 100.0}' | cut -d. -f1`.to_i
expect(usage).to_be_less_than(90)
end
# --- SITEMAP ---
check "The sitemap generator is available" do
make_sure SitemapGenerator.present?
end
check "sitemap.xml.gz exists", only: :production do
make_sure File.exist?(Rails.public_path.join("sitemap.xml.gz"))
end
# --- USAGE-DEPENDENT CHECKS ---
check "SolidQueue has processed jobs in the last 24 hours", only: :production do
make_sure SolidQueue::Job.where(created_at: 24.hours.ago..Time.now).order(created_at: :desc).limit(1).any?
end
# --- PAY / STRIPE ---
# TODO: no error webhooks in the past 24 hours, new sales in the past few hours, etc.