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.
@@ -0,0 +1,168 @@
1
+ <div class = "row">
2
+
3
+ <div class = "col">
4
+ <div class="card">
5
+ <div class="card-header">
6
+ Size
7
+ </div>
8
+ <div class="card-body">
9
+ <h1><%=round(@size)%>MB</h1>
10
+ </div>
11
+ </div>
12
+ </div>
13
+
14
+ <div class = "col">
15
+ <div class="card">
16
+ <div class="card-header">
17
+ Tables
18
+ </div>
19
+ <div class="card-body">
20
+ <h1><%=@tables%></h1>
21
+ </div>
22
+ </div>
23
+ </div>
24
+
25
+ <div class = "col">
26
+ <div class="card">
27
+ <div class="card-header">
28
+ Indexes
29
+ </div>
30
+ <div class="card-body">
31
+ <h1><%=@indexes%></h1>
32
+ </div>
33
+ </div>
34
+ </div>
35
+
36
+ <div class = "col">
37
+ <div class="card">
38
+ <div class="card-header">
39
+ Read queries
40
+ </div>
41
+ <div class="card-body">
42
+ <h1><%=format(@reads)%></h1>
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+ <div class = "col">
48
+ <div class="card">
49
+ <div class="card-header">
50
+ Write queries
51
+ </div>
52
+ <div class="card-body">
53
+ <h1><%=format(@writes)%></h1>
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+
59
+ </div>
60
+
61
+ <div class = "row">
62
+ &nbsp;<br/>
63
+ </div>
64
+
65
+ <div class = "row">
66
+
67
+ <div class = "col">
68
+ <div class="card">
69
+ <div class="card-header">
70
+ Reads vs writes over time
71
+ </div>
72
+ <div class="card-body">
73
+ <span class="hidden inlinecolumn"><%=Oj.dump([["Time", "Reads", "Writes"]] + @reads_vs_writes)%></span>
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ <div class = "col-6">
79
+ <div class="card">
80
+ <div class="card-header">
81
+ Reads vs writes
82
+ </div>
83
+ <div class="card-body">
84
+ <span class="hidden inlinepie"><%=[["name", "value"],["reads", @reads],["writes", @writes]]%></span>
85
+ </div>
86
+ </div>
87
+ </div>
88
+
89
+ </div>
90
+
91
+ <div class = "row">
92
+ &nbsp;<br/>
93
+ </div>
94
+
95
+ <div class = "row">
96
+
97
+ <div class = "col">
98
+ <div class="card">
99
+ <div class="card-header">
100
+ Query execution time over time
101
+ </div>
102
+ <div class="card-body">
103
+ <span class="hidden inlinecolumn"><%=Oj.dump([["Time", "Reads execution time", {'role' => 'tooltip'}, "Writes execution time", {'role' => 'tooltip'}]] + @reads_vs_writes_times)%></span>
104
+ </div>
105
+ </div>
106
+ </div>
107
+
108
+ <div class = "col-6">
109
+ <div class="card">
110
+ <div class="card-header">
111
+ Query time
112
+ </div>
113
+ <div class="card-body">
114
+ <span class="hidden inlinepie"><%=[["name", "value"],["total reads time", @read_times],["total writes time", @write_times]]%></span>
115
+ </div>
116
+ </div>
117
+ </div>
118
+
119
+ </div>
120
+
121
+ <div class = "row">
122
+ &nbsp;<br/>
123
+ </div>
124
+
125
+ <div class = "row">
126
+
127
+ <div class = "col-6">
128
+ <div class="card">
129
+ <div class="card-header">
130
+ Slowest queries
131
+ </div>
132
+ <div class="card-body">
133
+ <table class="table">
134
+ <%@slowest.each do |r| %>
135
+ <tr>
136
+ <td><%=r['key']%></td>
137
+ <td align="right"><h6><%=format(r['ravg'].truncate(4))%>&nbsp;sec</h6></td>
138
+ </tr>
139
+ <% end %>
140
+ </table>
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <div class = "col-6">
146
+ <div class="card">
147
+ <div class="card-header">
148
+ Most expensive queries (execution time x frequency)
149
+ </div>
150
+ <div class="card-body">
151
+ <table class="table">
152
+ <%@popular.each do |r| %>
153
+ <tr>
154
+ <td><%=r['key']%></td>
155
+ <td align="right"><h6><%=format(r['rtotal'].truncate(4))%>&nbsp;sec</h6></td>
156
+ </tr>
157
+ <% end %>
158
+ </table>
159
+ </div>
160
+ </div>
161
+ </div>
162
+
163
+ </div>
164
+
165
+ <div class = "row">
166
+ &nbsp;<br/>
167
+ </div>
168
+
@@ -0,0 +1,151 @@
1
+ <div class = "row">
2
+
3
+ <div class = "col">
4
+ <div class="card">
5
+ <div class="card-header">
6
+ Size
7
+ </div>
8
+ <div class="card-body">
9
+ <h1><%=format(round(@size))%> MB</h1>
10
+ </div>
11
+ </div>
12
+ </div>
13
+
14
+ <div class = "col">
15
+ <div class="card">
16
+ <div class="card-header">
17
+ Active queues
18
+ </div>
19
+ <div class="card-body">
20
+ <h1><%=format(@queues.length)%></h1>
21
+ </div>
22
+ </div>
23
+ </div>
24
+
25
+ <div class = "col">
26
+ <div class="card">
27
+ <div class="card-header">
28
+ Outstanding jobs
29
+ </div>
30
+ <div class="card-body">
31
+ <h1><%=format(@jobs)%></h1>
32
+ </div>
33
+ </div>
34
+ </div>
35
+
36
+ </div>
37
+
38
+ <div class = "row">
39
+ &nbsp;<br/>
40
+ </div>
41
+
42
+
43
+ <div class="row">
44
+
45
+ <div class = "col">
46
+ <div class="card">
47
+ <div class="card-header">
48
+ Processed jobs
49
+ </div>
50
+ <div class="card-body">
51
+ <h1><%=format(@processed_count)%> jobs</h1>
52
+ </div>
53
+ </div>
54
+ </div>
55
+
56
+ <div class = "col">
57
+ <div class="card">
58
+ <div class="card-header">
59
+ Total processing time
60
+ </div>
61
+ <div class="card-body">
62
+ <h1><%=format(@processing_time)%> sec</h1>
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <div class = "col">
68
+ <div class="card">
69
+ <div class="card-header">
70
+ Average processing time per job
71
+ </div>
72
+ <div class="card-body">
73
+ <h1><%=(@processing_time.to_f / @processed_count).truncate(4) rescue 0%> sec</h1>
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ </div>
79
+
80
+ <div class = "row">
81
+ &nbsp;<br/>
82
+ </div>
83
+
84
+ <div class="row">
85
+
86
+ <div class = "col">
87
+ <div class="card">
88
+ <div class="card-header">
89
+ Processed jobs by queue
90
+ </div>
91
+ <div class="card-body">
92
+ <span class="hidden inlinepie">
93
+ <%=[["name", "value"]] + @processed_count_by_queue.to_a%>
94
+ </span>
95
+ </div>
96
+ </div>
97
+ </div>
98
+
99
+ <div class = "col">
100
+ <div class="card">
101
+ <div class="card-header">
102
+ Job processing time by queue
103
+ </div>
104
+ <div class="card-body">
105
+ <span class="hidden inlinepie">
106
+ <%=[["name", "value"]] + @processing_time_by_queue.to_a%>
107
+ </span>
108
+ </div>
109
+ </div>
110
+ </div>
111
+
112
+ </div>
113
+
114
+ <div class = "row">
115
+ &nbsp;<br/>
116
+ </div>
117
+
118
+ <div class="row">
119
+
120
+ <div class = "col">
121
+ <div class="card">
122
+ <div class="card-header">
123
+ Processed jobs by queue over time
124
+ </div>
125
+ <div class="card-body">
126
+ <span class="hidden inlinestackedcolumn">
127
+ <%=@processed_count_over_time_by_queues.to_a%>
128
+ </span>
129
+ </div>
130
+ </div>
131
+ </div>
132
+
133
+ <div class = "col">
134
+ <div class="card">
135
+ <div class="card-header">
136
+ Job processing time by queue over time
137
+ </div>
138
+ <div class="card-body">
139
+ <span class="hidden inlinestackedcolumn">
140
+ <%=@processing_time_over_time_by_queues.to_a%>
141
+ </span>
142
+ </div>
143
+ </div>
144
+ </div>
145
+
146
+ </div>
147
+
148
+ <div class = "row">
149
+ &nbsp;<br/>
150
+ </div>
151
+
@@ -24,52 +24,54 @@ class Litecable
24
24
  }
25
25
 
26
26
  def initialize(options = {})
27
- @messages = []
27
+ @messages = Litesupport::Pool.new(1){[]}
28
28
  init(options)
29
+ collect_metrics if @options[:metrics]
29
30
  end
30
31
 
31
32
  # broadcast a message to a specific channel
32
33
  def broadcast(channel, payload=nil)
33
34
  # group meesages and only do broadcast every 10 ms
34
- #run_stmt(:publish, channel.to_s, Oj.dump(payload), @pid)
35
35
  # but broadcast locally normally
36
- @mutex.synchronize{ @messages << [channel.to_s, Oj.dump(payload)] }
36
+ @messages.acquire{|msgs| msgs << [channel.to_s, Oj.dump(payload)]}
37
+ capture(:broadcast, channel)
37
38
  local_broadcast(channel, payload)
38
39
  end
39
40
 
40
41
  # subscribe to a channel, optionally providing a success callback proc
41
42
  def subscribe(channel, subscriber, success_callback = nil)
42
- @mutex.synchronize do
43
- @subscribers[channel] = {} unless @subscribers[channel]
44
- @subscribers[channel][subscriber] = true
43
+ @subscribers.acquire do |subs|
44
+ subs[channel] = {} unless subs[channel]
45
+ subs[channel][subscriber] = true
45
46
  end
47
+ capture(:subscribe, channel)
46
48
  end
47
49
 
48
50
  # unsubscribe from a channel
49
51
  def unsubscribe(channel, subscriber)
50
- @mutex.synchronize do
51
- @subscribers[channel].delete(subscriber) rescue nil
52
- end
52
+ @subscribers.acquire{|subs| subs[channel].delete(subscriber) rescue nil }
53
+ capture(:unsubscribe, channel)
53
54
  end
54
55
 
55
56
  private
56
-
57
+
58
+ # broadcast the message to local subscribers
57
59
  def local_broadcast(channel, payload=nil)
58
- return unless @subscribers[channel]
59
60
  subscribers = []
60
- @mutex.synchronize do
61
- subscribers = @subscribers[channel].keys
61
+ @subscribers.acquire do |subs|
62
+ return unless subs[channel]
63
+ subscribers = subs[channel].keys
62
64
  end
63
65
  subscribers.each do |subscriber|
64
66
  subscriber.call(payload)
67
+ capture(:message, channel)
65
68
  end
66
69
  end
67
70
 
68
71
  def setup
69
72
  super # create connection
70
73
  @pid = Process.pid
71
- @subscribers = {}
72
- @mutex = Litesupport::Mutex.new
74
+ @subscribers = Litesupport::Pool.new(1){{}}
73
75
  @running = true
74
76
  @listener = create_listener
75
77
  @pruner = create_pruner
@@ -80,14 +82,14 @@ class Litecable
80
82
  def create_broadcaster
81
83
  Litesupport.spawn do
82
84
  while @running do
83
- @mutex.synchronize do
84
- if @messages.length > 0
85
+ @messages.acquire do |msgs|
86
+ if msgs.length > 0
85
87
  run_sql("BEGIN IMMEDIATE")
86
- while msg = @messages.shift
88
+ while msg = msgs.shift
87
89
  run_stmt(:publish, msg[0], msg[1], @pid)
88
90
  end
89
91
  run_sql("END")
90
- end
92
+ end
91
93
  end
92
94
  sleep 0.02
93
95
  end
@@ -107,7 +109,6 @@ class Litecable
107
109
  Litesupport.spawn do
108
110
  while @running do
109
111
  @last_fetched_id ||= (run_stmt(:last_id)[0][0] || 0)
110
- @logger.info @last_fetched_id
111
112
  run_stmt(:fetch, @last_fetched_id, @pid).to_a.each do |msg|
112
113
  @logger.info "RECEIVED #{msg}"
113
114
  @last_fetched_id = msg[0]
@@ -119,20 +120,9 @@ class Litecable
119
120
  end
120
121
 
121
122
  def create_connection
122
- conn = super
123
- conn.wal_autocheckpoint = 10000
124
- sql = YAML.load_file("#{__dir__}/litecable.sql.yml")
125
- version = conn.get_first_value("PRAGMA user_version")
126
- sql["schema"].each_pair do |v, obj|
127
- if v > version
128
- conn.transaction do
129
- obj.each{|k, s| conn.execute(s)}
130
- conn.user_version = v
131
- end
132
- end
133
- end
134
- sql["stmts"].each { |k, v| conn.stmts[k.to_sym] = conn.prepare(v) }
135
- conn
123
+ super("#{__dir__}/litecable.sql.yml") do |conn|
124
+ conn.wal_autocheckpoint = 10000
125
+ end
136
126
  end
137
127
 
138
128
  end
@@ -17,7 +17,7 @@ stmts:
17
17
 
18
18
  last_id: SELECT max(id) FROM messages
19
19
 
20
- fetch: SELECT id, channel, value FROM messages WHERE id > $1 and pid != $2
20
+ fetch: SELECT id, channel, value, created_at FROM messages WHERE id > $1 and pid != $2
21
21
 
22
22
  prune: DELETE FROM messages WHERE created_at < (unixepoch() - $1)
23
23
 
@@ -236,23 +236,12 @@ class Litecache
236
236
  end
237
237
 
238
238
  def create_connection
239
- conn = super
240
- conn.cache_size = 2000
241
- conn.journal_size_limit = [(@options[:size]/2).to_i, @options[:min_size]].min
242
- conn.max_page_count = (@options[:size] / conn.page_size).to_i
243
- conn.case_sensitive_like = true
244
- sql = YAML.load_file("#{__dir__}/litecache.sql.yml")
245
- version = conn.get_first_value("PRAGMA user_version")
246
- sql["schema"].each_pair do |v, obj|
247
- if v > version
248
- conn.transaction do
249
- obj.each{|k, s| conn.execute(s)}
250
- conn.user_version = v
251
- end
252
- end
253
- end
254
- sql["stmts"].each { |k, v| conn.stmts[k.to_sym] = conn.prepare(v) }
255
- conn
239
+ super("#{__dir__}/litecache.sql.yml") do |conn|
240
+ conn.cache_size = 2000
241
+ conn.journal_size_limit = [(@options[:size]/2).to_i, @options[:min_size]].min
242
+ conn.max_page_count = (@options[:size] / conn.page_size).to_i
243
+ conn.case_sensitive_like = true
244
+ end
256
245
  end
257
246
 
258
247
  end
@@ -1,6 +1,9 @@
1
1
  # all components should require the support module
2
2
  require_relative 'litesupport'
3
3
 
4
+ # all measurable components should require the litemetric class
5
+ require_relative 'litemetric'
6
+
4
7
  # Litedb inherits from the SQLite3::Database class and adds a few initialization options
5
8
  class Litedb < ::SQLite3::Database
6
9
 
@@ -34,7 +37,11 @@ class Litedb < ::SQLite3::Database
34
37
 
35
38
  # return the size of the database file
36
39
  def size
37
- execute("SELECT s.page_size * c.page_count FROM pragma_page_size() as s, pragma_page_count() as c")[0][0]
40
+ execute("SELECT s.page_size * c.page_count FROM pragma_page_size() AS s, pragma_page_count() AS c")[0][0]
41
+ end
42
+
43
+ def schema_object_count(type = nil)
44
+ execute("SELECT count(*) FROM SQLITE_MASTER WHERE iif(?1 IS NOT NULL, type = ?1, TRUE)", type)[0][0]
38
45
  end
39
46
 
40
47
  # collect snapshot information
@@ -44,7 +51,9 @@ class Litedb < ::SQLite3::Database
44
51
  path: filename,
45
52
  journal_mode: journal_mode,
46
53
  synchronous: synchronous,
47
- size: size
54
+ size: size.to_f / (1024 * 1024),
55
+ tables: schema_object_count('table'),
56
+ indexes: schema_object_count('index')
48
57
  }
49
58
  }
50
59
  end
@@ -55,9 +55,12 @@ class Litemetric
55
55
  ## event capturing
56
56
  ##################
57
57
 
58
+ def current_time_slot
59
+ (Time.now.to_i / 300) * 300
60
+ end
58
61
 
59
62
  def capture(topic, event, key=event, value=nil)
60
- @collector.capture(topic, event, key, value)
63
+ @collector.capture(topic, event, key, value, current_time_slot)
61
64
  end
62
65
 
63
66
  def capture_snapshot(topic, state)
@@ -83,18 +86,18 @@ class Litemetric
83
86
  def events_summaries(topic, resolution, order, dir, search, count)
84
87
  search = "%#{search}%" if search
85
88
  if dir.downcase == "desc"
86
- run_stmt(:events_summaries, topic, resolution, order, search, count).to_a
89
+ run_stmt_hash(:events_summaries, topic, resolution, order, search, count)
87
90
  else
88
- run_stmt(:events_summaries_asc, topic, resolution, order, search, count).to_a
91
+ run_stmt_hash(:events_summaries_asc, topic, resolution, order, search, count)
89
92
  end
90
93
  end
91
94
 
92
95
  def keys_summaries(topic, event, resolution, order, dir, search, count)
93
96
  search = "%#{search}%" if search
94
97
  if dir.downcase == "desc"
95
- run_stmt(:keys_summaries, topic, event, resolution, order, search, count).to_a
98
+ run_stmt_hash(:keys_summaries, topic, event, resolution, order, search, count).to_a
96
99
  else
97
- run_stmt(:keys_summaries_asc, topic, event, resolution, order, search, count).to_a
100
+ run_stmt_hash(:keys_summaries_asc, topic, event, resolution, order, search, count).to_a
98
101
  end
99
102
  end
100
103
 
@@ -103,11 +106,11 @@ class Litemetric
103
106
  end
104
107
 
105
108
  def event_data_points(step, count, resolution, topic, event)
106
- run_stmt(:event_data_points, step, count, resolution, topic, event).to_a
109
+ run_stmt_hash(:event_data_points, step, count, resolution, topic, event).to_a
107
110
  end
108
111
 
109
112
  def key_data_points(step, count, resolution, topic, event, key)
110
- run_stmt(:key_data_points, step, count, resolution, topic, event, key).to_a
113
+ run_stmt_hash(:key_data_points, step, count, resolution, topic, event, key).to_a
111
114
  end
112
115
 
113
116
  def snapshot(topic)
@@ -130,7 +133,21 @@ class Litemetric
130
133
  ###################
131
134
 
132
135
  private
133
-
136
+
137
+ def run_stmt_hash(stmt, *args)
138
+ res = run_stmt(stmt, *args)
139
+ cols = run_stmt_method(stmt, :columns)
140
+ hashes = []
141
+ res.each do | row |
142
+ hash = {}
143
+ row.each_with_index do |field, i|
144
+ hash[cols[i]] = field
145
+ end
146
+ hashes << hash
147
+ end
148
+ hashes
149
+ end
150
+
134
151
  def exit_callback
135
152
  STDERR.puts "--- Litemetric detected an exit, flushing metrics"
136
153
  @running = false
@@ -156,25 +173,9 @@ class Litemetric
156
173
  end
157
174
 
158
175
  def create_connection
159
- conn = super
160
- conn.wal_autocheckpoint = 10000 # checkpoint after 10000 pages are written
161
- sql = YAML.load_file("#{__dir__}/litemetric.sql.yml")
162
- version = conn.get_first_value("PRAGMA user_version")
163
- sql["schema"].each_pair do |v, obj|
164
- if v > version
165
- begin
166
- conn.transaction do
167
- obj.each{|k, s| conn.execute(s)}
168
- conn.user_version = v
169
- end
170
- rescue Exception => e
171
- STDERR.puts e.message
172
- raise e
173
- end
174
- end
175
- end
176
- sql["stmts"].each { |k, v| conn.stmts[k.to_sym] = conn.prepare(v) }
177
- conn
176
+ super("#{__dir__}/litemetric.sql.yml") do |conn|
177
+ conn.wal_autocheckpoint = 10000 # checkpoint after 10000 pages are written
178
+ end
178
179
  end
179
180
 
180
181
  def create_flusher
@@ -228,13 +229,16 @@ class Litemetric
228
229
  end
229
230
 
230
231
  def measure(event, key=event)
231
- return yield unless @litemetric
232
+ unless @litemetric
233
+ yield
234
+ return 0
235
+ end
232
236
  t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
233
- res = yield
237
+ yield
234
238
  t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
235
- value = (( t2 - t1 ) * 1000).round # capture time in milliseconds
239
+ value = t2 - t1
236
240
  capture(event, key, value)
237
- res
241
+ value # return value so other events can reuse it
238
242
  end
239
243
 
240
244
  def capture_snapshot
@@ -273,16 +277,16 @@ class Litemetric
273
277
  init(options)
274
278
  end
275
279
 
276
- def capture(topic, event, key, value=nil)
280
+ def capture(topic, event, key, value=nil, time=nil)
277
281
  if key.is_a? Array
278
- key.each{|k| capture_single_key(topic, event, k, value)}
282
+ key.each{|k| capture_single_key(topic, event, k, value, time)}
279
283
  else
280
- capture_single_key(topic, event, key, value)
284
+ capture_single_key(topic, event, key, value, time)
281
285
  end
282
286
  end
283
287
 
284
- def capture_single_key(topic, event, key, value)
285
- run_stmt(:capture_event, topic.to_s, event.to_s, key.to_s, nil ,1, value)
288
+ def capture_single_key(topic, event, key, value, time=nil)
289
+ run_stmt(:capture_event, topic.to_s, event.to_s, key.to_s, time ,1, value)
286
290
  end
287
291
 
288
292
  def flush
@@ -302,37 +306,10 @@ class Litemetric
302
306
  end
303
307
 
304
308
  def create_connection
305
- conn = super
306
- conn.execute("ATTACH ? as m", @options[:dbpath].to_s)
307
- conn.wal_autocheckpoint = 10000
308
- sql = YAML.load_file("#{__dir__}/litemetric_collector.sql.yml")
309
- version = conn.get_first_value("PRAGMA user_version")
310
- sql["schema"].each_pair do |v, obj|
311
- if v > version
312
- conn.transaction do
313
- obj.each do |k, s|
314
- begin
315
- conn.execute(s)
316
- rescue Exception => e
317
- STDERR.puts "Error parsing #{k}"
318
- STDERR.puts s
319
- raise e
320
- end
321
- end
322
- conn.user_version = v
323
- end
324
- end
325
- end
326
- sql["stmts"].each do |k, v|
327
- begin
328
- conn.stmts[k.to_sym] = conn.prepare(v)
329
- rescue Exception => e
330
- STDERR.puts "Error parsing #{k}"
331
- STDERR.puts v
332
- raise e
333
- end
309
+ super("#{__dir__}/litemetric_collector.sql.yml") do |conn|
310
+ conn.execute("ATTACH ? as m", @options[:dbpath].to_s)
311
+ conn.wal_autocheckpoint = 10000
334
312
  end
335
- conn
336
313
  end
337
314
 
338
315
  end