litestack 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/Gemfile +2 -0
  4. data/README.md +5 -11
  5. data/assets/event_page.png +0 -0
  6. data/assets/index_page.png +0 -0
  7. data/assets/topic_page.png +0 -0
  8. data/bench/bench_jobs_rails.rb +1 -1
  9. data/bench/bench_jobs_raw.rb +1 -1
  10. data/bin/liteboard +81 -0
  11. data/lib/action_cable/subscription_adapter/litecable.rb +1 -11
  12. data/lib/generators/litestack/install/USAGE +11 -0
  13. data/lib/generators/litestack/install/install_generator.rb +35 -0
  14. data/lib/generators/litestack/install/templates/cable.yml +11 -0
  15. data/lib/generators/litestack/install/templates/database.yml +34 -0
  16. data/lib/litestack/liteboard/liteboard.rb +305 -0
  17. data/lib/litestack/liteboard/views/event.erb +32 -0
  18. data/lib/litestack/liteboard/views/index.erb +54 -0
  19. data/lib/litestack/liteboard/views/layout.erb +303 -0
  20. data/lib/litestack/liteboard/views/litecable.erb +118 -0
  21. data/lib/litestack/liteboard/views/litecache.erb +144 -0
  22. data/lib/litestack/liteboard/views/litedb.erb +168 -0
  23. data/lib/litestack/liteboard/views/litejob.erb +151 -0
  24. data/lib/litestack/liteboard/views/topic.erb +48 -0
  25. data/lib/litestack/litecable.rb +25 -35
  26. data/lib/litestack/litecable.sql.yml +1 -1
  27. data/lib/litestack/litecache.rb +31 -28
  28. data/lib/litestack/litedb.rb +124 -1
  29. data/lib/litestack/litejob.rb +2 -2
  30. data/lib/litestack/litejobqueue.rb +8 -8
  31. data/lib/litestack/litemetric.rb +177 -88
  32. data/lib/litestack/litemetric.sql.yml +312 -42
  33. data/lib/litestack/litemetric_collector.sql.yml +56 -0
  34. data/lib/litestack/litequeue.rb +28 -29
  35. data/lib/litestack/litequeue.sql.yml +11 -0
  36. data/lib/litestack/litesupport.rb +137 -57
  37. data/lib/litestack/railtie.rb +10 -0
  38. data/lib/litestack/version.rb +1 -1
  39. data/lib/litestack.rb +1 -0
  40. data/lib/sequel/adapters/litedb.rb +1 -1
  41. data/template.rb +7 -0
  42. metadata +81 -5
  43. data/lib/litestack/metrics_app.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73ffde7dabefb83339815ca79679b0916637d50a6c70a43d13e34f8b431f2914
4
- data.tar.gz: 44ef247be2a245801d6d5f25a383031941b6c6dc496fac551eca525b274da178
3
+ metadata.gz: a1125a2cb5a2d9ea277fb9a0774632eb297ed4d322882dd33776d8a8f1c6471e
4
+ data.tar.gz: 9cb917c999850b2bd668e8826a4be4f091ee4e8e0f963b54ed87f5b7a7b060d6
5
5
  SHA512:
6
- metadata.gz: 8fe11c9b2d655dc39fadedb20782701ad8fe00cf8d548b1312a031122c2af5d6e66c26ab69ad22f6beb39e72419c33cf0d4b30bfd058d5a93db261122f6b29ae
7
- data.tar.gz: 2ba9faecce2a926e9a8b65efe11c0766eb3ef4021510fb786e3a7a9483ff2d24f463968cb5ddab07465cf3eae2b04f28edb428589f3c95b1c7ebe6c4f826cc54
6
+ metadata.gz: e51460d99e66732e3dcd72e9444f1d739df7ae37804eb9b308cdae0faa7c9c34d4e7554e8f9d0bf91905f7ae3e9b4d7470509a84653da2ee736582b268fe188a
7
+ data.tar.gz: 873b188bd6db924f1e56034c22f5cc465b01d653ac925a2d30de648271b0b64a705894667b90005bd3d457ad318d3ee4736b16a60fa128c34d894172bc9b8e16
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
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
+
12
+ ## [0.2.6] - 2023-07-16
13
+
14
+ - Much improved database location setting (thanks Brad Gessler)
15
+ - A Rails generator for better Rails Litestack defaults (thanks Brad Gessler)
16
+ - Revamped Litemetric, now much faster and more accurate (still experimental)
17
+ - Introduced Liteboard, a dashboard for viewing Litemetric data
18
+
3
19
  ## [0.2.3] - 2023-05-20
4
20
 
5
21
  - Cut back on options defined in the Litejob Rails adapter
data/Gemfile CHANGED
@@ -6,3 +6,5 @@ source "https://rubygems.org"
6
6
  gemspec
7
7
 
8
8
 
9
+
10
+ gem "rack", "~> 3.0"
data/README.md CHANGED
@@ -31,19 +31,13 @@ Litestack is still pretty young and under heavy development, but you are welcome
31
31
 
32
32
  ## Installation
33
33
 
34
- Add this line to your application's Gemfile:
34
+ Add the `litestack` gem line to your application's Gemfile:
35
35
 
36
- ```ruby
37
- gem 'litestack'
38
- ```
39
-
40
- And then execute:
41
-
42
- $ bundle install
36
+ $ bundle add litestack
43
37
 
44
- Or install it yourself as:
38
+ To configure a Rails application to run the full litestack, run:
45
39
 
46
- $ gem install litestack
40
+ $ rails generate litestack:install
47
41
 
48
42
  ## Usage
49
43
 
@@ -189,7 +183,7 @@ production:
189
183
 
190
184
  ## Contributing
191
185
 
192
- 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.
193
187
 
194
188
  ## License
195
189
 
Binary file
Binary file
Binary file
@@ -29,7 +29,7 @@ if env == "a" # threaded
29
29
  end
30
30
 
31
31
  require_relative '../lib/active_job/queue_adapters/litejob_adapter'
32
- puts Litesupport.environment
32
+ puts Litesupport.scheduler
33
33
 
34
34
  RailsJob.queue_adapter = :litejob
35
35
  t = Time.now.to_f
@@ -28,7 +28,7 @@ end
28
28
 
29
29
  require './uljob.rb'
30
30
 
31
- STDERR.puts "litejob started in #{Litesupport.environment} environmnet"
31
+ STDERR.puts "litejob started in #{Litesupport.scheduler} environmnet"
32
32
 
33
33
  t = Process.clock_gettime(Process::CLOCK_MONOTONIC)
34
34
  bench("enqueuing litejobs", count) do |i|
data/bin/liteboard ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'optparse'
5
+ require_relative '../lib/litestack/liteboard/liteboard'
6
+ DEFAULTS = {
7
+ config_path: Litemetric::DEFAULT_OPTIONS[:config_path],
8
+ path: Litemetric::DEFAULT_OPTIONS[:path]
9
+ }
10
+
11
+ options = {
12
+ config_path: nil,
13
+ path: nil,
14
+ deamonize: false,
15
+ Port: 9292,
16
+ Host: 'localhost',
17
+ environment: 'production',
18
+ quiet: false
19
+ }
20
+
21
+ OptionParser.new do |parser|
22
+ parser.banner = "Usage: liteboard [options]"
23
+ parser.on("-d", "--database PATH", "path to SQLite file (default: #{DEFAULTS[:path]})") { |v| options[:path] = v }
24
+ parser.on("-c", "--config PATH", "path to a litemetric config file (default: #{DEFAULTS[:config_path]}) ") { |v| options[:config_path] = v }
25
+ parser.on("-s", "--server SERVER", "use SERVER (e.g. puma/falcon/iodine)") { |v| options[:port] = v }
26
+ parser.on("-H", "--host HOST", "listen on HOST (default: #{options[:Host]})") { |v| options[:Host] = v }
27
+ parser.on("-p", "--port PORT", "use PORT (default: #{options[:Port]})") { |v| options[:Port] = v.to_i rescue options[:Port] }
28
+ parser.on("-D", "--deamonize", "run in the background") { |v| options[:deamonize] = true }
29
+ parser.on("-E", "--env ENVIRONMENT", "which environment to use (default: #{options[:environment]})") { |v| options[:environment] = v }
30
+ parser.on("-q", "--quiet", "turn off logging") { |v| options[:quiet] = true }
31
+ parser.on("-h", "--help", "print this message") do
32
+ puts parser
33
+ exit
34
+ end
35
+ end.parse!
36
+
37
+ def check_database(path)
38
+ unless File.exist?(path)
39
+ puts "liteboard: missing database file, please ensure the db path is correct"
40
+ puts "liteboard: exiting"
41
+ exit
42
+ end
43
+ end
44
+
45
+ check_database(options[:path]) if options[:path]
46
+
47
+ # if there is a config file then we need to check it
48
+ config = nil
49
+ if options[:config_path]
50
+ begin
51
+ config = Yaml.load_file(options[:config_path])
52
+ rescue
53
+ puts "liteboard: missing or bad config file, please ensure the config file path is correct"
54
+ puts "liteboard: exiting"
55
+ exit
56
+ end
57
+ else # no config path! use the default
58
+ config = Yaml.load_file(DEFAULTS[:config_path]) rescue nil
59
+ end
60
+
61
+ if config
62
+ if options[:path].nil?
63
+ path = config['path'] || config[options[:environment]]['path']
64
+ options[:path] = path
65
+ end
66
+ end
67
+
68
+ # if still no path we assume a default db path
69
+ options[:path] = DEFAULTS[:path] if options[:path].nil?
70
+
71
+
72
+ # check the validity of the path before starting the server
73
+ check_database(options[:path])
74
+ Litemetric.options = options
75
+ litemetric = Litemetric.instance
76
+ options[:app] = Liteboard.app
77
+
78
+ require_relative '../lib/litestack'
79
+ puts "Starting Liteboard version #{Litestack::VERSION}"
80
+
81
+ Rack::Server.start(options)
@@ -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
@@ -0,0 +1,11 @@
1
+ Description:
2
+ Installs litestack gem and configures the project to use litestack
3
+ in development, test, and production environments.
4
+
5
+ Example:
6
+ bin/rails generate litestack:install
7
+
8
+ This will modify:
9
+ config/environments/production.rb
10
+ config/database.yml
11
+ config/cable.yml
@@ -0,0 +1,35 @@
1
+ class Litestack::InstallGenerator < Rails::Generators::Base
2
+ source_root File.expand_path("templates", __dir__)
3
+
4
+ # Force copy configuratioon files so Rails installs don't ask questions
5
+ # that less experienced people might not understand. The more Sr folks.
6
+ # will know to check git to look at what changed.
7
+ def modify_database_adapater
8
+ copy_file "database.yml", "config/database.yml", force: true
9
+ end
10
+
11
+ def modify_action_cable_adapter
12
+ copy_file "cable.yml", "config/cable.yml", force: true
13
+ end
14
+
15
+ def modify_cache_store_adapter
16
+ gsub_file "config/environments/production.rb",
17
+ "# config.cache_store = :mem_cache_store",
18
+ "config.cache_store = :litecache"
19
+ end
20
+
21
+ def modify_active_job_adapter
22
+ gsub_file "config/environments/production.rb",
23
+ "# config.active_job.queue_adapter = :resque",
24
+ "config.active_job.queue_adapter = :litejob"
25
+ end
26
+
27
+ def modify_gitignore
28
+ append_file ".gitignore", <<~TEXT
29
+
30
+ # Ignore default Litestack SQLite databases.
31
+ /db/**/*.sqlite3
32
+ /db/**/*.sqlite3-*
33
+ TEXT
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ development:
2
+ adapter: litecable
3
+
4
+ test:
5
+ adapter: test
6
+
7
+ staging:
8
+ adapter: litecable
9
+
10
+ production:
11
+ adapter: litecable
@@ -0,0 +1,34 @@
1
+ # SQLite. Versions 3.8.0 and up are supported.
2
+ # gem install sqlite3
3
+ #
4
+ # Ensure the SQLite 3 gem is defined in your Gemfile
5
+ # gem "sqlite3"
6
+ #
7
+ # `Litesupport.root.join("data.sqlite3")` stores
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
+ #
13
+ default: &default
14
+ adapter: litedb
15
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
16
+ idle_timeout: 0
17
+ database: <%= Litesupport.root.join("data.sqlite3") %>
18
+
19
+ development:
20
+ <<: *default
21
+
22
+ # Warning: The database defined as "test" will be erased and
23
+ # re-generated from your development database when you run "rake".
24
+ # Do not set this db to the same as development or production.
25
+ test:
26
+ <<: *default
27
+
28
+ # Warning: Make sure your production database path is on a persistent
29
+ # volume, otherwise your application data could be deleted between deploys.
30
+ #
31
+ # You may also set the Litesupport.root in production via the
32
+ # `LITESTACK_DATA_PATH` environment variable.
33
+ production:
34
+ <<: *default
@@ -0,0 +1,305 @@
1
+ # frozen_string_literal: true
2
+ require 'hanami/router'
3
+ require 'tilt'
4
+ require 'erubi'
5
+
6
+ # require relative so we pick the gem version that corresponds to the liteboard binary
7
+ require_relative '../../litestack/litemetric'
8
+
9
+ class Liteboard
10
+
11
+ @@resolutions = {'minute' => [300, 12], 'hour' => [3600, 24], 'day' => [3600*24, 7], 'week' => [3600*24*7, 53], 'year' => [3600*24*365, 100] }
12
+ @@res_mapping = {'hour' => 'minute', 'day' => 'hour', 'week' => 'day', 'year' => 'week'}
13
+ @@templates = {}
14
+ @@app = Hanami::Router.new do
15
+
16
+ get "/", to: ->(env) do
17
+ Liteboard.new(env).call(:index)
18
+ end
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)
26
+ end
27
+
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)
34
+ end
35
+
36
+ end
37
+
38
+
39
+ def initialize(env)
40
+ @env = env
41
+ @params = @env["router.params"]
42
+ @running = true
43
+ @lm = Litemetric.instance
44
+ end
45
+
46
+ def params(key)
47
+ URI.decode_uri_component("#{@params[key]}")
48
+ end
49
+
50
+ def call(method)
51
+ before
52
+ res = send(method)
53
+ after(res)
54
+ end
55
+
56
+ def after(body=nil)
57
+ [200, {'Cache-Control' => 'no-cache'}, [body]]
58
+ end
59
+
60
+ def before
61
+ @res = params(:res) || 'day'
62
+ @resolution = @@res_mapping[@res]
63
+ if not @resolution
64
+ @res = 'day'
65
+ @resolution = @@res_mapping[@res]
66
+ end
67
+ @step = @@resolutions[@resolution][0]
68
+ @count = @@resolutions[@resolution][1]
69
+ @order = params(:order)
70
+ @order = nil if @order == ''
71
+ @dir = params(:dir)
72
+ @dir = 'desc' if @dir.nil? || @dir == ''
73
+ @dir = @dir.downcase
74
+ @idir = if @dir == "asc"
75
+ "desc"
76
+ else
77
+ "asc"
78
+ end
79
+ @search = params(:search)
80
+ @search = nil if @search == ''
81
+ @topics = @lm.topic_summaries(@resolution, @step * @count, @order, @dir, @search)
82
+ end
83
+
84
+ def index
85
+ @order = 'topic' unless @order
86
+ @topics.each do |topic|
87
+ data_points = @lm.topic_data_points(@step, @count, @resolution, topic[0])
88
+ topic << data_points.collect{|r| [r[0],r[2] || 0]}
89
+ end
90
+ render :index
91
+ end
92
+
93
+ def litecache
94
+ @order = 'rcount' unless @order
95
+ @topic = 'Litecache'
96
+ @events = @lm.events_summaries(@topic, @resolution, @order, @dir, @search, @step * @count)
97
+ @events.each do |event|
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']]}
101
+ end
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]}
129
+ end
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
150
+ end
151
+
152
+ def litejob
153
+ @order = 'rcount' unless @order
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}"
225
+ end
226
+
227
+ def index_sort_url(field)
228
+ "/?#{compose_query(field)}"
229
+ end
230
+
231
+ def topic_sort_url(field)
232
+ "/topics/#{encode(@topic)}?#{compose_query(field)}"
233
+ end
234
+
235
+ def event_sort_url(field)
236
+ "/topics/#{encode(@topic)}/events/#{encode(@event)}?#{compose_query(field)}"
237
+ end
238
+
239
+ def compose_query(field)
240
+ field.downcase!
241
+ "res=#{@res}&order=#{field}&dir=#{@order == field ? @idir : @dir}&search=#{@search}"
242
+ end
243
+
244
+ def sorted?(field)
245
+ @order == field
246
+ end
247
+
248
+ def dir(field)
249
+ if sorted?(field)
250
+ if @dir == 'asc'
251
+ return "<span class='material-icons'>arrow_drop_up</span>"
252
+ else
253
+ return "<span class='material-icons'>arrow_drop_down</span>"
254
+ end
255
+ end
256
+ '&nbsp;&nbsp;'
257
+ end
258
+
259
+ def encode(text)
260
+ URI.encode_uri_component(text)
261
+ end
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
+
276
+ def self.app
277
+ @@app
278
+ end
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
+
300
+ end
301
+
302
+
303
+
304
+
305
+ #Rack::Server.start({app: Litebaord.app, daemonize: false})
@@ -0,0 +1,32 @@
1
+ <h5><a href="/?res=<%=@res%>">All Topics</a> > <a href="/topics/<%=@topic%>?res=<%=@res%>"><%=@topic%></a> > <%= @event %></h5>
2
+ <div id="search"><form><input id="search-field" type="text" placeholder="Search keys" onkeydown="search_kd(this)" onkeyup="search_ku(this)" value="<%=@search%>"/></form></div>
3
+ <table class="table sortable">
4
+ <tr>
5
+ <th width="16%" class="<%='sorted' if @order == 'key'%>"><a href="<%=event_sort_url('key')%>">Key</a> <%=dir('key')%></th>
6
+ <th width="8%" class="<%='sorted' if @order == 'rcount'%>"><a href="<%=event_sort_url('rcount')%>">Event Count</a> <%=dir('rcount')%></th>
7
+ <th width="8%" class="<%='sorted' if @order == 'ravg'%>"><a href="<%=event_sort_url('ravg')%>">Avg Value</a> <%=dir('ravg')%></th>
8
+ <th width="8%" class="<%='sorted' if @order == 'rtotal'%>"><a href="<%=event_sort_url('rtotal')%>">Total Value</a> <%=dir('rtotal')%></th>
9
+ <th width="8%" class="<%='sorted' if @order == 'rmin'%>"><a href="<%=event_sort_url('rmin')%>">Min Value</a> <%=dir('rmin')%></th>
10
+ <th width="8%" class="<%='sorted' if @order == 'rmax'%>"><a href="<%=event_sort_url('rmax')%>">Max Value</a> <%=dir('rmax')%></th>
11
+ <th width="22%">Events over time</th>
12
+ <th width="22%">Average value over time</th>
13
+ </tr>
14
+ <% @keys.each do |key|%>
15
+ <tr>
16
+ <td title="<%=key[0]%>"><div class="label"><span><%=key[0]%></span></div></td>
17
+ <td><%=key[1]%></td>
18
+ <td><%="%0.2f" % [key[2]] if key[2]%></td>
19
+ <td><%="%0.2f" % [key[3]] if key[3]%></td>
20
+ <td><%="%0.2f" % [key[4]] if key[4]%></td>
21
+ <td><%="%0.2f" % [key[5]] if key[5]%></td>
22
+ <td class="chart"><span class="inlinecolumn hidden" data-label="Count"><%=Oj.dump(key[6]) if key[7]%></span></td>
23
+ <td class="chart"><span class="inlinecolumn hidden" data-label="Avg Value"><%=Oj.dump(key[7]) if key[7]%></span></td>
24
+ </tr>
25
+ <% end %>
26
+ <% if @keys.empty? %>
27
+ <tr>
28
+ <td class="empty" colspan="8">No data to display</td>
29
+ </tr>
30
+ <% end %>
31
+ </table>
32
+