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.
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