wackamole 0.0.3 → 0.0.4
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.
- data/Rakefile +1 -0
- data/bin/setup_indexes +44 -0
- data/bin/wackamole +8 -6
- data/config.ru +18 -0
- data/lib/app.rb +8 -5
- data/lib/controllers/dashboard.rb +13 -1
- data/lib/controllers/features.rb +4 -0
- data/lib/controllers/logs.rb +14 -5
- data/lib/controllers/mission.rb +19 -12
- data/lib/controllers/users.rb +7 -0
- data/lib/helpers/dashboard_helper.rb +7 -7
- data/lib/helpers/logs_helper.rb +29 -12
- data/lib/helpers/mission_helper.rb +21 -7
- data/lib/wackamole.rb +1 -1
- data/lib/wackamole/models/control.rb +45 -22
- data/lib/wackamole/models/log.rb +6 -4
- data/lib/wackamole/models/mission.rb +44 -132
- data/lib/wackamole/models/mole_info.rb +21 -15
- data/lib/wackamole/models/search_filter.rb +25 -11
- data/public/images/browsers_sprite.png +0 -0
- data/public/images/fault_small.png +0 -0
- data/public/images/perf_small.png +0 -0
- data/public/stylesheets/wackamole.css +135 -111
- data/spec/models/log_spec.rb +4 -4
- data/spec/models/mission_spec.rb +13 -192
- data/spec/models/search_filter_spec.rb +37 -9
- data/views/dashboard/_report.erb +2 -2
- data/views/dashboard/index.erb +2 -0
- data/views/features/index.erb +3 -1
- data/views/layout.erb +7 -7
- data/views/logs/_rows.erb +4 -3
- data/views/logs/index.erb +3 -1
- data/views/logs/show.erb +4 -8
- data/views/mission/_report.erb +66 -40
- data/views/mission/index.erb +1 -3
- data/views/mission/refresh_js.erb +5 -2
- data/views/mission/trash.txt +50 -0
- data/views/shared/_filter.erb +8 -9
- data/views/shared/_search.erb +2 -2
- data/views/users/index.erb +3 -1
- metadata +18 -2
data/lib/wackamole.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Wackamole
|
2
2
|
|
3
3
|
# :stopdoc:
|
4
|
-
VERSION = '0.0.
|
4
|
+
VERSION = '0.0.4' unless defined? Wackamole::VERSION
|
5
5
|
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR unless defined? Wackamole::LIBPATH
|
6
6
|
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR unless defined? Wackamole::PATH
|
7
7
|
# :startdoc:
|
@@ -3,6 +3,22 @@ require 'logger'
|
|
3
3
|
|
4
4
|
module Wackamole
|
5
5
|
class Control
|
6
|
+
|
7
|
+
# -----------------------------------------------------------------------
|
8
|
+
# Initialize app by reading off mongo configuration parameters if necessary
|
9
|
+
def self.init_config( config_file, env )
|
10
|
+
begin
|
11
|
+
config = YAML.load_file( config_file )
|
12
|
+
@config = config[env]
|
13
|
+
raise "Invalid environment `#{env}" unless @config
|
14
|
+
raise "Unable to find host in - #{@config.inspect}" unless @config.has_key?('host')
|
15
|
+
raise "Unable to find port in - #{@config.inspect}" unless @config.has_key?('port')
|
16
|
+
rescue => boom
|
17
|
+
@config = nil
|
18
|
+
raise "Hoy! An error occur loading the config file `#{config_file} -- #{boom}"
|
19
|
+
end
|
20
|
+
@config
|
21
|
+
end
|
6
22
|
|
7
23
|
# -------------------------------------------------------------------------
|
8
24
|
# Defines mole db identity
|
@@ -76,25 +92,6 @@ module Wackamole
|
|
76
92
|
@db = nil
|
77
93
|
db( db_name )
|
78
94
|
end
|
79
|
-
|
80
|
-
# -----------------------------------------------------------------------
|
81
|
-
# Initialize app by reading off mongo configuration parameters if necessary
|
82
|
-
# BOZO !!
|
83
|
-
# File.join( ENV['HOME'], %w[.wackamole wackamole.yml] )
|
84
|
-
#
|
85
|
-
def self.init_config( config_file, env )
|
86
|
-
begin
|
87
|
-
config = YAML.load_file( config_file )
|
88
|
-
@config = config[env]
|
89
|
-
raise "Invalid environment `#{env}" unless @config
|
90
|
-
raise "Unable to find host in - #{@config.inspect}" unless @config.has_key?('host')
|
91
|
-
raise "Unable to find port in - #{@config.inspect}" unless @config.has_key?('port')
|
92
|
-
rescue => boom
|
93
|
-
@config = nil
|
94
|
-
raise "Hoy! An error occur loading the config file `#{config_file} -- #{boom}"
|
95
|
-
end
|
96
|
-
@config
|
97
|
-
end
|
98
95
|
|
99
96
|
def self.config
|
100
97
|
raise "You must call init_config before using this object" unless @config
|
@@ -119,13 +116,39 @@ module Wackamole
|
|
119
116
|
return @db if @db and @db.name == db_name
|
120
117
|
raise "No database specified" unless db_name
|
121
118
|
@db = connection.db( db_name, opts )
|
122
|
-
ensure_indexes
|
123
|
-
@db
|
124
119
|
end
|
125
120
|
|
126
121
|
# -----------------------------------------------------------------------
|
127
122
|
# Make sure the right indexes are set
|
128
|
-
def self.ensure_indexes
|
123
|
+
def self.ensure_indexes!( set=true, drop_first=false, show_info=false, clear_only=false )
|
124
|
+
mole_databases.each do |db_name|
|
125
|
+
db = db( db_name )
|
126
|
+
|
127
|
+
puts "-"*100
|
128
|
+
puts "Database - #{db_name}"
|
129
|
+
|
130
|
+
if drop_first or clear_only
|
131
|
+
puts "[WARNING] Dropping indexes on all Rackamole collections"
|
132
|
+
%w(logs features users).each do |c|
|
133
|
+
db[c].drop_indexes
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
if set and !clear_only
|
138
|
+
puts "[INFO] Creating indexes on all Rackamole collections..."
|
139
|
+
Wackamole::Log.ensure_indexes!
|
140
|
+
Wackamole::Feature.ensure_indexes!
|
141
|
+
Wackamole::User.ensure_indexes!
|
142
|
+
end
|
143
|
+
|
144
|
+
if show_info
|
145
|
+
%w(logs features users).each do |c|
|
146
|
+
indexes = db[c].index_information
|
147
|
+
puts "\tIndexes on collection `#{c}"
|
148
|
+
indexes.keys.sort.each { |k| puts "\t\tIndex: %-30s -- %s" % [k,indexes[k].join( ", ")] }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
129
152
|
end
|
130
153
|
end
|
131
154
|
end
|
data/lib/wackamole/models/log.rb
CHANGED
@@ -29,16 +29,18 @@ module Wackamole
|
|
29
29
|
indexes = logs_cltn.index_information
|
30
30
|
created_count = 0
|
31
31
|
|
32
|
-
[:
|
32
|
+
[:typ, :fid].each do |name|
|
33
33
|
unless indexes.has_key?( "#{name}_1" )
|
34
34
|
logs_cltn.create_index( name )
|
35
35
|
created_count += 1
|
36
36
|
end
|
37
37
|
end
|
38
|
-
unless indexes.
|
39
|
-
logs_cltn.create_index( [
|
40
|
-
created_count += 1
|
38
|
+
unless indexes.key?( "did_-1_tid_-1_type_-1" )
|
39
|
+
logs_cltn.create_index( [[:did, Mongo::DESCENDING], [:tid, Mongo::DESCENDING], [:type, Mongo::DESCENDING] ] )
|
41
40
|
end
|
41
|
+
unless indexes.key?( "fid_-1_did_-1" )
|
42
|
+
logs_cltn.create_index( [[:fid, Mongo::DESCENDING], [:did, Mongo::DESCENDING], [:type, Mongo::DESCENDING] ] )
|
43
|
+
end
|
42
44
|
created_count
|
43
45
|
end
|
44
46
|
end
|
@@ -4,73 +4,57 @@ module Wackamole
|
|
4
4
|
class Mission
|
5
5
|
extend ::SingleForwardable
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
rollups_cltn.update( { :app => app }, { '$set' => { "envs.#{env}.#{type}" => 0 } } )
|
7
|
+
# -----------------------------------------------------------------------
|
8
|
+
# Pick up moled application pulse
|
9
|
+
def self.pulse( last_tick )
|
10
|
+
to_date = count_logs
|
11
|
+
today = count_logs( last_tick, true )
|
12
|
+
last_tick = count_logs( last_tick )
|
13
|
+
{ :to_date => to_date, :today => today, :last_tick => last_tick }
|
15
14
|
end
|
16
|
-
|
17
|
-
#
|
18
|
-
#
|
19
|
-
def self.
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
envs_info = rollup[envs].keys
|
27
|
-
envs_info.each do |env|
|
28
|
-
db_name = Wackamole::Control.to_mole_db( app, env )
|
29
|
-
delete_list << env unless databases.include?( db_name )
|
30
|
-
end
|
31
|
-
# if app is no longer around blow away the rollup
|
32
|
-
if delete_list.size == envs_info.size
|
33
|
-
rollups_cltn.remove( { :_id => rollup['_id'] } )
|
34
|
-
else
|
35
|
-
delete_list.each do |env|
|
36
|
-
rollup[envs].delete( env )
|
37
|
-
end
|
38
|
-
rollups_cltn.save( rollup, :safe => true ) unless delete_list.empty?
|
39
|
-
end
|
15
|
+
|
16
|
+
# -----------------------------------------------------------------------
|
17
|
+
# generates mole logs conditons
|
18
|
+
def self.gen_conds( now, single_day )
|
19
|
+
conds = {}
|
20
|
+
if now
|
21
|
+
date_id = now.to_date_id.to_s
|
22
|
+
time_id = now.to_time_id
|
23
|
+
conds[:did] = date_id
|
24
|
+
conds[:tid] = {'$gte' => time_id} unless single_day
|
40
25
|
end
|
26
|
+
conds
|
41
27
|
end
|
42
28
|
|
43
|
-
#
|
44
|
-
#
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
row = { :app => app_name, envs => { env => { type_name => count } } }
|
66
|
-
rollups_cltn.insert( row, :safe => true )
|
67
|
-
end
|
29
|
+
# -----------------------------------------------------------------------
|
30
|
+
# Compute mole counts for each moled apps
|
31
|
+
def self.count_logs( now=nil, single_day=false )
|
32
|
+
counts = {}
|
33
|
+
conds = gen_conds( now, single_day )
|
34
|
+
# elapsed = Benchmark.realtime do
|
35
|
+
Wackamole::Control.mole_databases.each do |db_name|
|
36
|
+
db = Wackamole::Control.db( db_name )
|
37
|
+
app_name, env = Wackamole::Control.extract_app_info( db_name )
|
38
|
+
logs_cltn = db['logs']
|
39
|
+
|
40
|
+
totals = { Rackamole.feature => 0, Rackamole.perf => 0, Rackamole.fault => 0 }
|
41
|
+
if counts[app_name]
|
42
|
+
counts[app_name][env] = totals
|
43
|
+
else
|
44
|
+
counts[app_name] = { env => totals }
|
45
|
+
end
|
46
|
+
row = counts[app_name][env]
|
47
|
+
[Rackamole.feature, Rackamole.perf, Rackamole.fault].each do |t|
|
48
|
+
conds[:typ] = t
|
49
|
+
logs = logs_cltn.find( conds, :fields => [:_id] )
|
50
|
+
row[t] = logs.count
|
68
51
|
end
|
69
52
|
end
|
70
|
-
end
|
71
|
-
|
53
|
+
# end
|
54
|
+
# puts "Computing counts %d -- %5.4f" % [counts.size, elapsed]
|
55
|
+
counts
|
72
56
|
end
|
73
|
-
|
57
|
+
|
74
58
|
# =========================================================================
|
75
59
|
private
|
76
60
|
|
@@ -88,77 +72,5 @@ module Wackamole
|
|
88
72
|
raise "Invalid mole log type `#{type}"
|
89
73
|
end
|
90
74
|
end
|
91
|
-
|
92
|
-
# ---------------------------------------------------------------------------
|
93
|
-
# Check moled apps status - reports any perf/excep that occurred since the
|
94
|
-
# last check
|
95
|
-
def self.comb_applications( now, reset )
|
96
|
-
report = {}
|
97
|
-
Wackamole::Control.mole_databases.each do |db_name|
|
98
|
-
db = Wackamole::Control.db( db_name )
|
99
|
-
|
100
|
-
app_name, env = Wackamole::Control.extract_app_info( db_name )
|
101
|
-
totals = analyse_logs( db, now, reset )
|
102
|
-
|
103
|
-
if report[app_name]
|
104
|
-
report[app_name][envs][env] = totals
|
105
|
-
else
|
106
|
-
report[app_name] = { envs => { env => totals } }
|
107
|
-
end
|
108
|
-
end
|
109
|
-
report
|
110
|
-
end
|
111
|
-
|
112
|
-
# -------------------------------------------------------------------------
|
113
|
-
# Report on possible application issues
|
114
|
-
def self.amend_report( report, app_name, env, log )
|
115
|
-
type = log['typ']
|
116
|
-
|
117
|
-
if report[app_name]
|
118
|
-
env_info = report[app_name][envs]
|
119
|
-
if env_info[env]
|
120
|
-
env_info[env][type] ? env_info[env][type] += 1 : env_info[env][type] = 1
|
121
|
-
else
|
122
|
-
env_info[ env ] = { type => 1 }
|
123
|
-
end
|
124
|
-
else
|
125
|
-
report[app_name] = { envs => { env => { type => 1 } } }
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
# -------------------------------------------------------------------------
|
130
|
-
# envs key
|
131
|
-
def self.envs() @envs ||= 'envs'; end
|
132
|
-
|
133
|
-
# -------------------------------------------------------------------------
|
134
|
-
# computes counts for each mole types
|
135
|
-
def self.analyse_logs( db, now, reset )
|
136
|
-
check_types = [Rackamole.perf, Rackamole.fault, Rackamole.feature]
|
137
|
-
date_id = now.to_date_id.to_s
|
138
|
-
time_id = now.to_time_id
|
139
|
-
|
140
|
-
if reset
|
141
|
-
conds = {
|
142
|
-
:did => date_id,
|
143
|
-
:typ => { '$in' => check_types }
|
144
|
-
}
|
145
|
-
else
|
146
|
-
conds = {
|
147
|
-
:did => { '$gte' => date_id },
|
148
|
-
:tid => { '$gte' => time_id },
|
149
|
-
:typ => { '$in' => check_types }
|
150
|
-
}
|
151
|
-
end
|
152
|
-
|
153
|
-
logs = db['logs'].find( conds, :fields => ['typ', 'rti', 'fault', 'fid'] )
|
154
|
-
totals =
|
155
|
-
{
|
156
|
-
Rackamole.feature => 0,
|
157
|
-
Rackamole.perf => 0,
|
158
|
-
Rackamole.fault => 0
|
159
|
-
}
|
160
|
-
logs.each { |log| totals[log['typ']] += 1 }
|
161
|
-
totals
|
162
|
-
end
|
163
75
|
end
|
164
76
|
end
|
@@ -12,7 +12,9 @@ module Wackamole
|
|
12
12
|
# TODO - PERF - try just using cursor vs to_a
|
13
13
|
def self.collect_dashboard_info( now )
|
14
14
|
info = {}
|
15
|
-
day_logs = logs_cltn.find( { :did => now.to_date_id.to_s },
|
15
|
+
day_logs = logs_cltn.find( { :did => now.to_date_id.to_s },
|
16
|
+
:fields => [:typ, :fid, :tid, :did, :uid],
|
17
|
+
:sort => [ [:tid => Mongo::ASCENDING] ] ).to_a
|
16
18
|
|
17
19
|
# Fetch user count for this hour
|
18
20
|
users = day_logs.inject( Set.new ) do |set,log|
|
@@ -44,27 +46,31 @@ module Wackamole
|
|
44
46
|
end
|
45
47
|
|
46
48
|
# Count all logs per hourly time period
|
47
|
-
|
48
|
-
|
49
|
+
hours = (0...24).to_a
|
50
|
+
hour_info = hours.inject(OrderedHash.new) { |res,hour| res[hour] = { :user => 0, :feature => 0, :perf => 0, :fault => 0 };res }
|
49
51
|
user_per_hour = {}
|
50
52
|
day_logs.each do |log|
|
51
53
|
date_tokens = log['did'].match( /(\d{4})(\d{2})(\d{2})/ ).captures
|
52
|
-
time_tokens = log['tid'].match( /(\d{2})(\d{2})(\d{2})/ ).captures
|
54
|
+
time_tokens = log['tid'].match( /(\d{2})(\d{2})(\d{2})/ ).captures
|
55
|
+
|
53
56
|
utc = Time.utc( date_tokens[0], date_tokens[1], date_tokens[2], time_tokens[0], time_tokens[1], time_tokens[2] )
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
57
|
+
local = utc.localtime
|
58
|
+
hour = local.hour
|
59
|
+
current_day = local.day
|
60
|
+
next if local.day != current_day
|
61
|
+
if user_per_hour[hour]
|
62
|
+
unless user_per_hour[hour].include? log['uid']
|
63
|
+
hour_info[hour][:user] += 1
|
64
|
+
user_per_hour[hour] << log['uid']
|
59
65
|
end
|
60
66
|
else
|
61
|
-
user_per_hour[
|
62
|
-
|
67
|
+
user_per_hour[hour] = [ log['uid'] ]
|
68
|
+
hour_info[hour][:user] += 1
|
63
69
|
end
|
64
70
|
case log['typ']
|
65
|
-
when Rackamole.feature :
|
66
|
-
when Rackamole.perf :
|
67
|
-
when Rackamole.fault :
|
71
|
+
when Rackamole.feature : hour_info[hour][:feature] += 1 if features.add?( log['fid'])
|
72
|
+
when Rackamole.perf : hour_info[hour][:perf] += 1
|
73
|
+
when Rackamole.fault : hour_info[hour][:fault] += 1
|
68
74
|
end
|
69
75
|
end
|
70
76
|
|
@@ -73,7 +79,7 @@ module Wackamole
|
|
73
79
|
info[:fault_series] = []
|
74
80
|
info[:perf_series] = []
|
75
81
|
info[:feature_series] = []
|
76
|
-
|
82
|
+
hour_info.values.map do |hash|
|
77
83
|
info[:user_series] << hash[:user]
|
78
84
|
info[:fault_series] << hash[:fault]
|
79
85
|
info[:perf_series] << hash[:perf]
|
@@ -39,11 +39,17 @@ module Wackamole
|
|
39
39
|
@types ||= %w[All Feature Perf Fault]
|
40
40
|
end
|
41
41
|
|
42
|
+
# ---------------------------------------------------------------------------
|
43
|
+
# Set filter type
|
44
|
+
def mole_type( type )
|
45
|
+
self.type = to_filter_type( type )
|
46
|
+
end
|
47
|
+
|
42
48
|
# ---------------------------------------------------------------------------
|
43
49
|
# Find all features
|
44
50
|
def features
|
45
51
|
rows = Feature.features_cltn.find().to_a
|
46
|
-
features = rows.map { |f| [context_for(f), f['_id']] }
|
52
|
+
features = rows.map { |f| [context_for(f), f['_id'].to_s] }
|
47
53
|
features.sort! { |a,b| a.first <=> b.first }
|
48
54
|
features.insert( 0, ["All", -1] )
|
49
55
|
end
|
@@ -117,7 +123,7 @@ module Wackamole
|
|
117
123
|
end
|
118
124
|
|
119
125
|
if browser_type != 'All'
|
120
|
-
conds[
|
126
|
+
conds["bro.name"] = browser_type
|
121
127
|
end
|
122
128
|
|
123
129
|
# filter mole_features
|
@@ -135,7 +141,7 @@ module Wackamole
|
|
135
141
|
if key
|
136
142
|
if key == "user"
|
137
143
|
users = Wackamole::User.users_cltn.find( { :una => Regexp.new( tokens.first ) }, :fields => ['_id'] )
|
138
|
-
conds[field_map(
|
144
|
+
conds[field_map( 'user_id' )] = { '$in' => users.collect{ |u| u['_id'] } }
|
139
145
|
elsif tokens.size == 2
|
140
146
|
conds["#{field_map(key)}.#{tokens.first}"] = Regexp.new( tokens.last )
|
141
147
|
elsif tokens.size == 1
|
@@ -154,16 +160,24 @@ module Wackamole
|
|
154
160
|
# ---------------------------------------------------------------------------
|
155
161
|
# Search filter key name map
|
156
162
|
def field_map( key )
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
when :host : :hos
|
161
|
-
when :session : :ses
|
162
|
-
when :params : :par
|
163
|
-
else key
|
164
|
-
end
|
163
|
+
field = Rackamole::Store::MongoDb.field_map[key.to_sym]
|
164
|
+
raise "Unable to map attribute `#{key}" unless field
|
165
|
+
field
|
165
166
|
end
|
166
167
|
|
168
|
+
def to_filter_type( type )
|
169
|
+
case type
|
170
|
+
when Rackamole.feature
|
171
|
+
'Feature'
|
172
|
+
when Rackamole.perf
|
173
|
+
'Perf'
|
174
|
+
when Rackamole.fault
|
175
|
+
'Fault'
|
176
|
+
else
|
177
|
+
raise "Invalid rackamole type `#{type}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
167
181
|
# Map named type to fixnum value
|
168
182
|
def map_mole_type( type )
|
169
183
|
case type
|