litestack 0.2.6 → 0.3.0

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