litestack 0.2.6 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6db833e61264ea2036338102d0ed1d3e9eb0a5d94a235db5a8f8d4cf390c65f
4
- data.tar.gz: 5a48dd08c18ab39f2a19462025e351da8f3e2eedf55f779019cef3ed277e12bc
3
+ metadata.gz: a1125a2cb5a2d9ea277fb9a0774632eb297ed4d322882dd33776d8a8f1c6471e
4
+ data.tar.gz: 9cb917c999850b2bd668e8826a4be4f091ee4e8e0f963b54ed87f5b7a7b060d6
5
5
  SHA512:
6
- metadata.gz: a6dd503c63cb0ca49092f9f612a6c087d07b13eefa083ca930931b956e5cf81357f130932e2514b340d2e240b73df377bf3f5a55516b7bf444bbe4149c60656c
7
- data.tar.gz: a1749b7eba0f8d155fc5e18b7a2da8d26df25ee4bf954e8c15724b8cb8164fb9f23aeab9387793420714e27fd2e13d357a91ece891dfca49afb7dbf6f8a91564
6
+ metadata.gz: e51460d99e66732e3dcd72e9444f1d739df7ae37804eb9b308cdae0faa7c9c34d4e7554e8f9d0bf91905f7ae3e9b4d7470509a84653da2ee736582b268fe188a
7
+ data.tar.gz: 873b188bd6db924f1e56034c22f5cc465b01d653ac925a2d30de648271b0b64a705894667b90005bd3d457ad318d3ee4736b16a60fa128c34d894172bc9b8e16
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2023-08-13
4
+
5
+ - Reworked the Litecable thread safety model
6
+ - Fixed multiple litejob bugs (thanks Stephen Margheim)
7
+ - Fixed Railtie dependency (thanks Marco Roth)
8
+ - Litesupport fixes (thanks Stephen Margheim)
9
+ - Much improved metrics reporting for Litedb, Litecache, Litejob & Litecable
10
+ - Removed (for now, will come again later) litemetric reporting support for ad-hoc modules
11
+
3
12
  ## [0.2.6] - 2023-07-16
4
13
 
5
14
  - Much improved database location setting (thanks Brad Gessler)
data/README.md CHANGED
@@ -183,7 +183,7 @@ production:
183
183
 
184
184
  ## Contributing
185
185
 
186
- Bug reports are welcome on GitHub at https://github.com/oldmoe/litestack. Please note that this is not an open contribution project and that we don't accept pull requests.
186
+ Bug reports and pull requests are welcome on GitHub at https://github.com/oldmoe/litestack.
187
187
 
188
188
  ## License
189
189
 
Binary file
Binary file
Binary file
@@ -10,20 +10,10 @@ module ActionCable
10
10
 
11
11
  prepend ChannelPrefix
12
12
 
13
- DEFAULT_OPTIONS = {
14
- config_path: "./config/litecable.yml",
15
- path: "./db/cable.db",
16
- sync: 0, # no need to sync at all
17
- mmap_size: 16 * 1024 * 1024, # 16MB of memory hold hot messages
18
- expire_after: 10, # remove messages older than 10 seconds
19
- listen_interval: 0.005, # check new messages every 5 milliseconds
20
- metrics: false
21
- }
22
-
23
13
  def initialize(server, logger=nil)
24
14
  @server = server
25
15
  @logger = server.logger
26
- super(DEFAULT_OPTIONS.dup)
16
+ super({config_path: "./config/litecable.yml"})
27
17
  end
28
18
 
29
19
  def shutdown
@@ -6,10 +6,14 @@
6
6
  #
7
7
  # `Litesupport.root.join("data.sqlite3")` stores
8
8
  # application data in the path `./db/#{Rails.env}/data.sqlite3`
9
+ #
10
+ # idle_timeout should be set to zero, to avoid recycling sqlite connections
11
+ # and losing the page cache
12
+ #
9
13
  default: &default
10
14
  adapter: litedb
11
15
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
12
- timeout: 5000
16
+ idle_timeout: 0
13
17
  database: <%= Litesupport.root.join("data.sqlite3") %>
14
18
 
15
19
  development:
@@ -16,13 +16,21 @@ class Liteboard
16
16
  get "/", to: ->(env) do
17
17
  Liteboard.new(env).call(:index)
18
18
  end
19
-
20
- get "/topics/:topic", to: ->(env) do
21
- Liteboard.new(env).call(:topic)
19
+
20
+ get "/topics/Litejob", to: ->(env) do
21
+ Liteboard.new(env).call(:litejob)
22
+ end
23
+
24
+ get "/topics/Litecache", to: ->(env) do
25
+ Liteboard.new(env).call(:litecache)
22
26
  end
23
27
 
24
- get "/topics/:topic/events/:event", to: ->(env) do
25
- Liteboard.new(env).call(:event)
28
+ get "/topics/Litedb", to: ->(env) do
29
+ Liteboard.new(env).call(:litedb)
30
+ end
31
+
32
+ get "/topics/Litecable", to: ->(env) do
33
+ Liteboard.new(env).call(:litecable)
26
34
  end
27
35
 
28
36
  end
@@ -45,12 +53,6 @@ class Liteboard
45
53
  after(res)
46
54
  end
47
55
 
48
- def render(tpl_name)
49
- layout = Tilt.new("#{__dir__}/views/layout.erb")
50
- tpl = Tilt.new("#{__dir__}/views/#{tpl_name.to_s}.erb")
51
- res = layout.render(self){tpl.render(self)}
52
- end
53
-
54
56
  def after(body=nil)
55
57
  [200, {'Cache-Control' => 'no-cache'}, [body]]
56
58
  end
@@ -76,48 +78,150 @@ class Liteboard
76
78
  end
77
79
  @search = params(:search)
78
80
  @search = nil if @search == ''
81
+ @topics = @lm.topic_summaries(@resolution, @step * @count, @order, @dir, @search)
79
82
  end
80
83
 
81
84
  def index
82
85
  @order = 'topic' unless @order
83
- topics = @lm.topics
84
- @topics = @lm.topic_summaries(@resolution, @step * @count, @order, @dir, @search)
85
86
  @topics.each do |topic|
86
87
  data_points = @lm.topic_data_points(@step, @count, @resolution, topic[0])
87
- topic << data_points.collect{|r| [r[0],r[2]]}
88
+ topic << data_points.collect{|r| [r[0],r[2] || 0]}
88
89
  end
89
90
  render :index
90
91
  end
91
92
 
92
- def topic
93
+ def litecache
93
94
  @order = 'rcount' unless @order
94
- @topic = params(:topic)
95
+ @topic = 'Litecache'
95
96
  @events = @lm.events_summaries(@topic, @resolution, @order, @dir, @search, @step * @count)
96
97
  @events.each do |event|
97
- data_points = @lm.event_data_points(@step, @count, @resolution, @topic, event[0])
98
- event << data_points.collect{|r| [r[0],r[2]]}
99
- event << data_points.collect{|r| [r[0],r[3]]}
98
+ data_points = @lm.event_data_points(@step, @count, @resolution, @topic, event['name'])
99
+ event['counts'] = data_points.collect{|r| [r['rtime'],r['rcount']]}
100
+ event['values'] = data_points.collect{|r| [r['rtime'],r['ravg']]}
100
101
  end
101
- @snapshot = @lm.snapshot(@topic)
102
- if @snapshot.empty?
103
- @snapshot = []
104
- else
105
- @snapshot[0] = Oj.load(@snapshot[0]) unless @snapshot[0].nil?
102
+ @snapshot = read_snapshot(@topic)
103
+ @size = @snapshot[0][:summary][:size] rescue 0
104
+ @max_size = @snapshot[0][:summary][:max_size] rescue 0
105
+ @full = (@size / @max_size)*100 rescue 0
106
+ @entries = @snapshot[0][:summary][:entries] rescue 0
107
+ @gets = @events.find{|t| t['name'] == 'get'}
108
+ @sets = @events.find{|t| t['name'] == 'set'}
109
+ @reads = @gets['rcount'] rescue 0
110
+ @writes = @sets['rcount'] rescue 0
111
+ @hitrate = @gets['ravg'] rescue 0
112
+ @hits = @reads * @hitrate
113
+ @misses = @reads - @hits
114
+ @reads_vs_writes = @gets['counts'].collect.with_index{|obj, i| obj.clone << @sets['counts'][i][1] } rescue []
115
+ @hits_vs_misses = @gets['values'].collect.with_index{|obj, i| [obj[0], obj[1].to_f * @gets['counts'][i][1].to_f, (1 - obj[1].to_f) * @gets['counts'][i][1].to_f] } rescue []
116
+ @top_reads = @lm.keys_summaries(@topic, 'get', @resolution, @order, @dir, nil, @step * @count).first(8)
117
+ @top_writes = @lm.keys_summaries(@topic, 'set', @resolution, @order, @dir, nil, @step * @count).first(8)
118
+ render :litecache
119
+ end
120
+
121
+ def litedb
122
+ @order = 'rcount' unless @order
123
+ @topic = 'Litedb'
124
+ @events = @lm.events_summaries(@topic, @resolution, @order, @dir, @search, @step * @count)
125
+ @events.each do |event|
126
+ data_points = @lm.event_data_points(@step, @count, @resolution, @topic, event['name'])
127
+ event['counts'] = data_points.collect{|r| [r['rtime'],r['rcount'] || 0]}
128
+ event['values'] = data_points.collect{|r| [r['rtime'],r['rtotal'] || 0]}
106
129
  end
107
- render :topic
130
+ @snapshot = read_snapshot(@topic)
131
+ @size = @snapshot[0][:summary][:size] rescue 0
132
+ @tables = @snapshot[0][:summary][:tables] rescue 0
133
+ @indexes = @snapshot[0][:summary][:indexes] rescue 0
134
+ @gets = @events.find{|t| t['name'] == 'Read'}
135
+ @sets = @events.find{|t| t['name'] == 'Write'}
136
+ @reads = @gets['rcount'] rescue 0
137
+ @writes = @sets['rcount'] rescue 0
138
+ @time = @gets['ravg'] rescue 0
139
+ @reads_vs_writes = @gets['counts'].collect.with_index{|obj, i| obj.clone << @sets['counts'][i][1] } rescue []
140
+ @reads_vs_writes_times = @gets['values'].collect.with_index{|obj, i| [obj[0], obj[1], @sets['values'][i][1].to_f] } rescue []
141
+ @read_times = @gets['rtotal'] rescue 0
142
+ @write_times = @sets['rtotal'] rescue 0
143
+ @slowest = @lm.keys_summaries(@topic, 'Read', @resolution, 'ravg', 'desc', nil, @step * @count).first(8)
144
+ @slowest += @lm.keys_summaries(@topic, 'Write', @resolution, 'ravg', 'desc', nil, @step * @count).first(8)
145
+ @slowest = @slowest.sort{|a, b| a['ravg'] <=> b['ravg']}.reverse.first(8)
146
+ @popular = @lm.keys_summaries(@topic, 'Read', @resolution, 'rtotal', 'desc', nil, @step * @count).first(8)
147
+ @popular += @lm.keys_summaries(@topic, 'Write', @resolution, 'rtotal', 'desc', nil, @step * @count).first(8)
148
+ @popular = @popular.sort{|a, b| a['rtotal'] <=> b['rtotal']}.reverse.first(8)
149
+ render :litedb
108
150
  end
109
151
 
110
- def event
152
+ def litejob
111
153
  @order = 'rcount' unless @order
112
- @topic = params(:topic)
113
- @event = params(:event)
114
- @keys = @lm.keys_summaries(@topic, @event, @resolution, @order, @dir, @search, @step * @count)
115
- @keys.each do |key|
116
- data_points = @lm.key_data_points(@step, @count, @resolution, @topic, @event, key[0])
117
- key << data_points.collect{|r| [r[0],r[2]]}
118
- key << data_points.collect{|r| [r[0],r[3]]}
119
- end
120
- render :event
154
+ @topic = 'Litejob'
155
+ @events = @lm.events_summaries(@topic, @resolution, @order, @dir, @search, @step * @count)
156
+ @events.each do |event|
157
+ data_points = @lm.event_data_points(@step, @count, @resolution, @topic, event['name'])
158
+ event['counts'] = data_points.collect{|r| [r['rtime'],r['rcount'] || 0]}
159
+ event['values'] = data_points.collect{|r| [r['rtime'],r['rtotal'] || 0]}
160
+ end
161
+ @snapshot = read_snapshot(@topic)
162
+ @size = @snapshot[0][:summary][:size] rescue 0
163
+ @jobs = @snapshot[0][:summary][:jobs] rescue 0
164
+ @queues = @snapshot[0][:queues] rescue {}
165
+ @processed_jobs = @events.find{|e|e['name'] == 'perform'}
166
+ @processed_count = @processed_jobs['rcount'] rescue 0
167
+ @processing_time = @processed_jobs['rtotal'] rescue 0
168
+ keys_summaries = @lm.keys_summaries(@topic, 'perform', @resolution, 'rcount', 'desc', nil, @step * @count)
169
+ @processed_count_by_queue = keys_summaries.collect{|r|[r['key'], r['rcount']]}
170
+ @processing_time_by_queue = keys_summaries.collect{|r|[r['key'], r['rtotal']]} #.sort{|r1, r2| r1['rtotal'] > r2['rtotal'] }
171
+ @processed_count_over_time = @events.find{|e| e['name'] == 'perform'}['counts'] rescue []
172
+ @processing_time_over_time = @events.find{|e| e['name'] == 'perform'}['values'] rescue []
173
+ @processed_count_over_time_by_queues = []
174
+ @processing_time_over_time_by_queues = []
175
+ keys = ['Time']
176
+ keys_summaries.each_with_index do |summary,i|
177
+ key = summary['key']
178
+ keys << key
179
+ data_points = @lm.key_data_points(@step, @count, @resolution, @topic, 'perform', key)
180
+ if i == 0
181
+ data_points.each do |dp|
182
+ @processed_count_over_time_by_queues << [dp['rtime']]
183
+ @processing_time_over_time_by_queues << [dp['rtime']]
184
+ end
185
+ end
186
+ data_points.each_with_index do |dp, j|
187
+ @processed_count_over_time_by_queues[j] << (dp['rcount'] || 0)
188
+ @processing_time_over_time_by_queues[j] << (dp['rtotal'] || 0)
189
+ end
190
+ end
191
+ @processed_count_over_time_by_queues.unshift(keys)
192
+ @processing_time_over_time_by_queues.unshift(keys)
193
+ render :litejob
194
+ end
195
+
196
+ def litecable
197
+ @order = 'rcount' unless @order
198
+ @topic = 'Litecable'
199
+ @events = @lm.events_summaries(@topic, @resolution, @order, @dir, @search, @step * @count)
200
+ @events.each do |event|
201
+ data_points = @lm.event_data_points(@step, @count, @resolution, @topic, event['name'])
202
+ event['counts'] = data_points.collect{|r| [r['rtime'],r['rcount'] || 0]}
203
+ end
204
+
205
+ @subscription_count = @events.find{|t| t['name'] == 'subscribe'}['rcount'] rescue 0
206
+ @broadcast_count = @events.find{|t| t['name'] == 'broadcast'}['rcount'] rescue 0
207
+ @message_count = @events.find{|t| t['name'] == 'message'}['rcount'] rescue 0
208
+
209
+ @subscriptions_over_time = @events.find{|t| t['name'] == 'subscribe'}['counts'] rescue []
210
+ @broadcasts_over_time = @events.find{|t| t['name'] == 'broadcast'}['counts'] rescue []
211
+ @messages_over_time = @events.find{|t| t['name'] == 'message'}['counts'] rescue []
212
+ @messages_over_time = @messages_over_time.collect.with_index{|msg, i| [msg[0], @broadcasts_over_time[i][1], msg[1]]}
213
+
214
+ @top_subscribed_channels = @lm.keys_summaries(@topic, 'subscribe', @resolution, @order, @dir, @search, @step * @count).first(8)
215
+ @top_messaged_channels = @lm.keys_summaries(@topic, 'message', @resolution, @order, @dir, @search, @step * @count).first(8)
216
+ render :litecable
217
+ end
218
+
219
+ def index_url
220
+ "/?res=#{@res}&order=#{@order}&dir=#{@dir}&search=#{@search}"
221
+ end
222
+
223
+ def topic_url(topic)
224
+ "/topics/#{encode(topic)}?res=#{@res}&order=#{@order}&dir=#{@dir}&search=#{@search}"
121
225
  end
122
226
 
123
227
  def index_sort_url(field)
@@ -156,10 +260,43 @@ class Liteboard
156
260
  URI.encode_uri_component(text)
157
261
  end
158
262
 
263
+ def round(float)
264
+ return 0 unless float.is_a? Numeric
265
+ ((float * 100).round).to_f / 100
266
+ end
267
+
268
+ def format(float)
269
+ string = float.to_s
270
+ whole, decimal = string.split('.')
271
+ whole = whole.split('').reverse.each_slice(3).map(&:join).join(',').reverse
272
+ whole = [whole, decimal].join('.') if decimal
273
+ whole
274
+ end
275
+
159
276
  def self.app
160
277
  @@app
161
278
  end
162
279
 
280
+ private
281
+
282
+ def read_snapshot(topic)
283
+ snapshot = @lm.snapshot(topic)
284
+ if snapshot.empty?
285
+ snapshot = []
286
+ else
287
+ snapshot[0] = Oj.load(snapshot[0]) unless snapshot[0].nil?
288
+ end
289
+ snapshot
290
+ end
291
+
292
+ def render(tpl_name)
293
+ layout = Tilt.new("#{__dir__}/views/layout.erb")
294
+ tpl_path = "#{__dir__}/views/#{tpl_name.to_s}.erb"
295
+ tpl = Tilt.new(tpl_path)
296
+ res = layout.render(self){tpl.render(self)}
297
+ end
298
+
299
+
163
300
  end
164
301
 
165
302
 
@@ -1,22 +1,54 @@
1
1
 
2
- <h5>All topics</h5>
3
- <div id="search"><form><input id="search-field" type="text" placeholder="Search topics" onkeydown="search_kd(this)" onkeyup="search_ku(this)" value="<%=@search%>"/></form></div>
4
- <table class="sortable table">
5
- <tr>
6
- <th width="20%" class="<%='sorted' if @order == 'topic'%>"><a href="<%=index_sort_url('topic')%>">Topics</a> <%=dir('topic')%></th>
7
- <th width="20%" class="<%='sorted' if @order == 'rcount'%>"><a href="<%=index_sort_url('rcount')%>">Total events</a> <%=dir('rcount')%></th>
8
- <th width="40%">Events over time</th>
9
- </tr>
10
- <% @topics.each do |topic|%>
11
- <tr>
12
- <td title="<%=topic[0]%>"><div class="label"><a href="./topics/<%=encode(topic[0])%>?res=<%=@res%>"><%=topic[0]%></a></div></td>
13
- <td><%=topic[3]%></td>
14
- <td class="chart"><span class="inlinecolumn hidden" data-label="Count"><%=Oj.dump(topic[4]) if topic[4]%></span></td>
15
- </tr>
16
- <%end%>
17
- <% if @topics.empty? %>
18
- <tr>
19
- <td class="empty" colspan="5">No data to display</td>
20
- </tr>
21
- <% end %>
22
2
 
3
+
4
+
5
+ <div class="container">
6
+ <% @topics.each do |topic|%>
7
+
8
+ <div class = "row justify-content-center">
9
+
10
+ <div class = "col-6">
11
+ <div class="card">
12
+ <div class="card-header">
13
+ <a href="./topics/<%=encode(topic[0])%>?res=<%=@res%>"><%=topic[0]%></a>
14
+ </div>
15
+ <div class="card-body">
16
+ <div class="container">
17
+ <div class= "row">
18
+ <div class= "col">
19
+ <h1><%=topic[3]%> <span class="fs-4">events</span></h1>
20
+ </div>
21
+ <div class= "col">
22
+ <span class="inlineminicolumn hidden" data-label="Count"><%=Oj.dump(topic[4].unshift(['Time', 'Count'])) if topic[4]%></span>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ </div>
31
+
32
+ <div class = "row">
33
+ &nbsp;<br/>
34
+ </div>
35
+
36
+
37
+ <%end%>
38
+ <% if @topics.empty? %>
39
+ <div class = "row justify-content-center">
40
+
41
+ <div class = "col-6">
42
+ <div class="card">
43
+ <div class="card-header">
44
+ Topics
45
+ </div>
46
+ <div class="card-body justify-content-center">
47
+ No data to display
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ </div>
53
+ <%end%>
54
+ </div>