litestack 0.2.3 → 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 +4 -4
- data/CHANGELOG.md +16 -0
- data/Gemfile +2 -0
- data/README.md +5 -11
- data/assets/event_page.png +0 -0
- data/assets/index_page.png +0 -0
- data/assets/topic_page.png +0 -0
- data/bench/bench_jobs_rails.rb +1 -1
- data/bench/bench_jobs_raw.rb +1 -1
- data/bin/liteboard +81 -0
- data/lib/action_cable/subscription_adapter/litecable.rb +1 -11
- data/lib/generators/litestack/install/USAGE +11 -0
- data/lib/generators/litestack/install/install_generator.rb +35 -0
- data/lib/generators/litestack/install/templates/cable.yml +11 -0
- data/lib/generators/litestack/install/templates/database.yml +34 -0
- data/lib/litestack/liteboard/liteboard.rb +305 -0
- data/lib/litestack/liteboard/views/event.erb +32 -0
- data/lib/litestack/liteboard/views/index.erb +54 -0
- data/lib/litestack/liteboard/views/layout.erb +303 -0
- data/lib/litestack/liteboard/views/litecable.erb +118 -0
- data/lib/litestack/liteboard/views/litecache.erb +144 -0
- data/lib/litestack/liteboard/views/litedb.erb +168 -0
- data/lib/litestack/liteboard/views/litejob.erb +151 -0
- data/lib/litestack/liteboard/views/topic.erb +48 -0
- data/lib/litestack/litecable.rb +25 -35
- data/lib/litestack/litecable.sql.yml +1 -1
- data/lib/litestack/litecache.rb +31 -28
- data/lib/litestack/litedb.rb +124 -1
- data/lib/litestack/litejob.rb +2 -2
- data/lib/litestack/litejobqueue.rb +8 -8
- data/lib/litestack/litemetric.rb +177 -88
- data/lib/litestack/litemetric.sql.yml +312 -42
- data/lib/litestack/litemetric_collector.sql.yml +56 -0
- data/lib/litestack/litequeue.rb +28 -29
- data/lib/litestack/litequeue.sql.yml +11 -0
- data/lib/litestack/litesupport.rb +137 -57
- data/lib/litestack/railtie.rb +10 -0
- data/lib/litestack/version.rb +1 -1
- data/lib/litestack.rb +1 -0
- data/lib/sequel/adapters/litedb.rb +1 -1
- data/template.rb +7 -0
- metadata +81 -5
- data/lib/litestack/metrics_app.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1125a2cb5a2d9ea277fb9a0774632eb297ed4d322882dd33776d8a8f1c6471e
|
4
|
+
data.tar.gz: 9cb917c999850b2bd668e8826a4be4f091ee4e8e0f963b54ed87f5b7a7b060d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
34
|
+
Add the `litestack` gem line to your application's Gemfile:
|
35
35
|
|
36
|
-
|
37
|
-
gem 'litestack'
|
38
|
-
```
|
39
|
-
|
40
|
-
And then execute:
|
41
|
-
|
42
|
-
$ bundle install
|
36
|
+
$ bundle add litestack
|
43
37
|
|
44
|
-
|
38
|
+
To configure a Rails application to run the full litestack, run:
|
45
39
|
|
46
|
-
$
|
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.
|
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
|
data/bench/bench_jobs_rails.rb
CHANGED
data/bench/bench_jobs_raw.rb
CHANGED
@@ -28,7 +28,7 @@ end
|
|
28
28
|
|
29
29
|
require './uljob.rb'
|
30
30
|
|
31
|
-
STDERR.puts "litejob started in #{Litesupport.
|
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(
|
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,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
|
+
' '
|
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
|
+
|