litestack 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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>