wackamole 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/Rakefile +1 -0
  2. data/bin/setup_indexes +44 -0
  3. data/bin/wackamole +8 -6
  4. data/config.ru +18 -0
  5. data/lib/app.rb +8 -5
  6. data/lib/controllers/dashboard.rb +13 -1
  7. data/lib/controllers/features.rb +4 -0
  8. data/lib/controllers/logs.rb +14 -5
  9. data/lib/controllers/mission.rb +19 -12
  10. data/lib/controllers/users.rb +7 -0
  11. data/lib/helpers/dashboard_helper.rb +7 -7
  12. data/lib/helpers/logs_helper.rb +29 -12
  13. data/lib/helpers/mission_helper.rb +21 -7
  14. data/lib/wackamole.rb +1 -1
  15. data/lib/wackamole/models/control.rb +45 -22
  16. data/lib/wackamole/models/log.rb +6 -4
  17. data/lib/wackamole/models/mission.rb +44 -132
  18. data/lib/wackamole/models/mole_info.rb +21 -15
  19. data/lib/wackamole/models/search_filter.rb +25 -11
  20. data/public/images/browsers_sprite.png +0 -0
  21. data/public/images/fault_small.png +0 -0
  22. data/public/images/perf_small.png +0 -0
  23. data/public/stylesheets/wackamole.css +135 -111
  24. data/spec/models/log_spec.rb +4 -4
  25. data/spec/models/mission_spec.rb +13 -192
  26. data/spec/models/search_filter_spec.rb +37 -9
  27. data/views/dashboard/_report.erb +2 -2
  28. data/views/dashboard/index.erb +2 -0
  29. data/views/features/index.erb +3 -1
  30. data/views/layout.erb +7 -7
  31. data/views/logs/_rows.erb +4 -3
  32. data/views/logs/index.erb +3 -1
  33. data/views/logs/show.erb +4 -8
  34. data/views/mission/_report.erb +66 -40
  35. data/views/mission/index.erb +1 -3
  36. data/views/mission/refresh_js.erb +5 -2
  37. data/views/mission/trash.txt +50 -0
  38. data/views/shared/_filter.erb +8 -9
  39. data/views/shared/_search.erb +2 -2
  40. data/views/users/index.erb +3 -1
  41. 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.3' unless defined? Wackamole::VERSION
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
@@ -29,16 +29,18 @@ module Wackamole
29
29
  indexes = logs_cltn.index_information
30
30
  created_count = 0
31
31
 
32
- [:fid, :uid, :did, :tid].each do |name|
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.has_key?( 'did_-1_tid_-1' )
39
- logs_cltn.create_index( [ [:did, Mongo::DESCENDING], [:tid, Mongo::DESCENDING] ] )
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
- def self.rollups_cltn() @rollups ||= Wackamole::Control.collection( 'rollups', 'wackamole_mdb', :strict => false ); end
8
-
9
- def_delegators :rollups_cltn, :find, :find_one
10
-
11
- # -------------------------------------------------------------------------
12
- # Allows user to clear out perf or fault state til the next tick...
13
- def self.reset!( app, env, type )
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
- # Clean up rollups. Check if mole_db is still around
19
- def self.clean_up!
20
- databases = Wackamole::Control.mole_databases
21
- con = Wackamole::Control.connection
22
- rollups = rollups_cltn.find( {} )
23
- delete_list = []
24
- rollups.each do |rollup|
25
- app = rollup['app']
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
- # Retrieve reports if any...
45
- # BOZO !! Handle case where report is no longer valid - ie no mole db
46
- def self.rollups( now, reset )
47
- clean_up!
48
- rollups = comb_applications( now, reset )
49
- rollups.each_pair do |app_name, env_info|
50
- env_info[envs].each_pair do |env, info|
51
- info.each_pair do |mole_type, count|
52
- rollup = rollups_cltn.find_one( { :app => app_name } )
53
- type_name = to_type_name( mole_type )
54
- if rollup
55
- rollup_info = rollup[envs]
56
- if rollup_info and rollup_info[env]
57
- (rollup_info[env][type_name] and !reset) ? rollup_info[env][type_name] += count : rollup_info[env][type_name] = count
58
- elsif rollup_info
59
- rollup_info[env] = { type_name => count }
60
- # else
61
- # rollup_info[envs] = { env => { type_name => count } }
62
- end
63
- rollups_cltn.save( rollup, :safe => true )
64
- else
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
- find( {}, :sort => [ [:app, Mongo::ASCENDING], [:env, Mongo::ASCENDING] ] ).to_a
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 }, :fields => [:typ, :fid, :tid, :did, :uid] ).to_a
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
- times = (0...24).to_a
48
- time_info = times.inject(OrderedHash.new) { |res,time| res[time] = { :user => 0, :feature => 0, :perf => 0, :fault => 0 };res }
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
- time = utc.getlocal.hour
55
- if user_per_hour[time]
56
- unless user_per_hour[time].include? log['uid']
57
- time_info[time][:user] += 1
58
- user_per_hour[time] << log['uid']
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[time] = [ log['uid'] ]
62
- time_info[time][:user] += 1
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 : time_info[time][:feature] += 1 if features.add?( log['fid'])
66
- when Rackamole.perf : time_info[time][:perf] += 1
67
- when Rackamole.fault : time_info[time][:fault] += 1
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
- time_info.values.map do |hash|
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[:bro] = browser_type
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( key )] = { '$in' => users.collect{ |u| u['_id'] } }
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
- case key.to_sym
158
- when :user : :uid
159
- when :method : :met
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