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