appstats 0.10.0 → 0.11.2

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/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- appstats (0.10.0)
4
+ appstats (0.11.2)
5
5
  daemons
6
6
  net-scp
7
7
  rails (>= 2.3.0)
@@ -0,0 +1,16 @@
1
+ class CreateAppstatsJobs < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :appstats_result_jobs do |t|
4
+ t.string :name
5
+ t.string :frequency
6
+ t.string :status
7
+ t.text :query
8
+ t.datetime :last_run_at
9
+ t.timestamps
10
+ end
11
+ end
12
+
13
+ def self.down
14
+ drop_table :appstats_result_jobs
15
+ end
16
+ end
data/db/schema.rb CHANGED
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended to check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(:version => 20110217234136) do
13
+ ActiveRecord::Schema.define(:version => 20110222215437) do
14
14
 
15
15
  create_table "appstats_actions", :force => true do |t|
16
16
  t.string "name"
@@ -89,6 +89,16 @@ ActiveRecord::Schema.define(:version => 20110217234136) do
89
89
 
90
90
  add_index "appstats_log_collectors", ["host"], :name => "index_appstats_log_collectors_on_host"
91
91
 
92
+ create_table "appstats_result_jobs", :force => true do |t|
93
+ t.string "name"
94
+ t.string "frequency"
95
+ t.string "status"
96
+ t.text "query"
97
+ t.datetime "last_run_at"
98
+ t.datetime "created_at"
99
+ t.datetime "updated_at"
100
+ end
101
+
92
102
  create_table "appstats_results", :force => true do |t|
93
103
  t.string "name"
94
104
  t.string "result_type"
data/lib/appstats.rb CHANGED
@@ -13,6 +13,7 @@ require "#{File.dirname(__FILE__)}/appstats/log_collector"
13
13
  require "#{File.dirname(__FILE__)}/appstats/parser"
14
14
  require "#{File.dirname(__FILE__)}/appstats/query"
15
15
  require "#{File.dirname(__FILE__)}/appstats/result"
16
+ require "#{File.dirname(__FILE__)}/appstats/result_job"
16
17
  require "#{File.dirname(__FILE__)}/appstats/host"
17
18
  require "#{File.dirname(__FILE__)}/appstats/context_key"
18
19
  require "#{File.dirname(__FILE__)}/appstats/context_value"
@@ -115,37 +115,44 @@ module Appstats
115
115
  end
116
116
 
117
117
 
118
- m = input.match(/^this\s*(year|month|week|day)$/)
118
+ m = input.match(/^this\s*(year|quarter|month|week|day)$/)
119
119
  unless m.nil?
120
120
  range.from = EntryDate.parse(input)
121
- range.format = m[1] == "week" ? :inclusive : :fixed_point
121
+ range.format = ["week","quarter"].include?(m[1]) ? :inclusive : :fixed_point
122
122
  return range
123
123
  end
124
124
 
125
- m = input.match(/^(last|previous)\s*(year|month|week|day)$/)
125
+ m = input.match(/^(last|previous)\s*(year|quarter|month|week|day)$/)
126
126
  unless m.nil?
127
127
  range.from = EntryDate.parse(input)
128
128
  if m[2] == "week"
129
129
  range.to = range.from.end_of_week
130
130
  range.format = :inclusive
131
+ elsif m[2] == "quarter"
132
+ range.to = range.from.end_of_quarter
133
+ range.format = :inclusive
131
134
  else
132
135
  range.format = :fixed_point
133
136
  end
134
137
  return range
135
138
  end
136
139
 
137
- m = input.match(/^last\s*(.+)\s*(year|years|month|months|week|weeks|day|days)$/)
140
+ m = input.match(/^last\s*(.+)\s*(year|years|quarter|quarters|month|months|week|weeks|day|days)$/)
138
141
  unless m.nil?
139
142
  range.from = EntryDate.parse(input)
140
143
  range.format = :inclusive
141
144
  return range
142
145
  end
143
146
 
144
- m = input.match(/^previous\s*(.+)\s*(year|month|week|day)s?$/)
147
+ m = input.match(/^previous\s*(.+)\s*(year|quarter|month|week|day)s?$/)
145
148
  unless m.nil?
146
149
  range.from = EntryDate.parse(input)
147
150
  to = EntryDate.parse("last #{m[2]}")
148
- to = to.end_of_week if m[2] == "week"
151
+ if m[2] == "week"
152
+ to = to.end_of_week
153
+ elsif m[2] == "quarter"
154
+ to = to.end_of_quarter
155
+ end
149
156
  range.to = to
150
157
  range.format = :inclusive
151
158
  return range
@@ -22,6 +22,11 @@ module Appstats
22
22
  week
23
23
  end
24
24
 
25
+ def end_of_quarter
26
+ t = to_time.end_of_quarter
27
+ EntryDate.new(:year => t.year, :month => t.month)
28
+ end
29
+
25
30
  def to_time(mode = :start)
26
31
  return Time.now if @year.nil?
27
32
  t = Time.parse("#{@year}-#{@month||'01'}-#{@day||'01'} #{@hour||'00'}:#{@min||'00'}:#{@sec||'00'}")
@@ -79,6 +84,9 @@ module Appstats
79
84
  t_parts = [:year,:month,:day]
80
85
  elsif input.match(/^this year$/)
81
86
  t_parts = [:year]
87
+ elsif input.match(/^this quarter$/)
88
+ t = t.beginning_of_quarter
89
+ t_parts = [:year,:month]
82
90
  elsif input.match(/^this month$/)
83
91
  t_parts = [:year,:month]
84
92
  elsif input.match(/^this week$/)
@@ -99,6 +107,13 @@ module Appstats
99
107
  t -= last_date_offset(m).year
100
108
  t_parts = [:year]
101
109
  end
110
+
111
+ m = input.match(/^(last|previous)\s*(\d*)\s*quarters?$/)
112
+ if m
113
+ t = t.beginning_of_quarter
114
+ last_date_offset(m).times { t = (t - 1.day).beginning_of_quarter }
115
+ t_parts = [:year,:month]
116
+ end
102
117
 
103
118
  m = input.match(/^(last|previous)\s*(\d*)\s*months?$/)
104
119
  if m
@@ -11,6 +11,19 @@ module Appstats
11
11
  File.expand_path("#{File.dirname(__FILE__)}/../../log/appstats_remote_log_#{id}.log")
12
12
  end
13
13
 
14
+ def processed_filename
15
+ return filename if filename.nil? || filename == ''
16
+ m = filename.match(/(.*\/)(.*)/)
17
+ prefix = "__processed__"
18
+ return "#{prefix}#{filename}" if m.nil?
19
+ "#{m[1]}#{prefix}#{m[2]}"
20
+ end
21
+
22
+ def self.should_process(last_time)
23
+ return true if last_time.nil?
24
+ Time.now.day > last_time.day
25
+ end
26
+
14
27
  def self.find_remote_files(remote_login,path,log_template)
15
28
  begin
16
29
  Appstats.log(:info,"Looking for logs in [#{remote_login[:user]}@#{remote_login[:host]}:#{path}] labelled [#{log_template}]")
@@ -120,7 +133,28 @@ module Appstats
120
133
  Appstats.log(:info,"Processed #{count} file(s) with #{total_entries} entr(ies).")
121
134
  count
122
135
  end
136
+
137
+ def self.remove_remote_files(remote_login)
138
+ all = LogCollector.where("status = 'processed'").all
139
+ if all.empty?
140
+ Appstats.log(:info,"No remote logs to remove.")
141
+ return 0
142
+ end
143
+
144
+ count = 0
145
+ Appstats.log(:info,"About to remove #{all.size} remote file(s) from the processing queue.")
146
+ all.each do |log_collector|
147
+ Net::SSH.start(remote_login[:host], remote_login[:user], :password => remote_login[:password] ) do |ssh|
148
+ ssh.exec!("mv #{log_collector.filename} #{log_collector.processed_filename}")
149
+ Appstats.log(:info," - #{remote_login[:user]}@#{remote_login[:host]}:#{log_collector.processed_filename}")
150
+ log_collector.status = "destroyed"
151
+ log_collector.save
152
+ count += 1
153
+ end
154
+ end
155
+ Appstats.log(:info,"Removed #{count} remote file(s).")
156
+ count
157
+ end
123
158
 
124
159
  end
125
-
126
160
  end
@@ -7,9 +7,11 @@ module Appstats
7
7
 
8
8
  @@nill_query = "select 0 from appstats_entries LIMIT 1"
9
9
  @@default = "1=1"
10
- attr_accessor :query, :action, :host, :date_range, :query_to_sql, :contexts
10
+ attr_accessor :name, :query, :action, :host, :date_range, :query_to_sql, :contexts
11
11
 
12
12
  def initialize(data = {})
13
+ @name = data[:name]
14
+ @result_type = data[:result_type] || "on_demand"
13
15
  self.query=(data[:query])
14
16
  end
15
17
 
@@ -19,7 +21,7 @@ module Appstats
19
21
  end
20
22
 
21
23
  def run
22
- result = Appstats::Result.new(:result_type => :on_demand, :query => @query, :query_as_sql => @query_to_sql, :action => @action, :host => @host, :from_date => @date_range.from_date, :to_date => @date_range.to_date, :contexts => @contexts)
24
+ result = Appstats::Result.new(:name => @name, :result_type => @result_type, :query => @query, :query_as_sql => @query_to_sql, :action => @action, :host => @host, :from_date => @date_range.from_date, :to_date => @date_range.to_date, :contexts => @contexts)
23
25
  result.count = ActiveRecord::Base.connection.select_one(@query_to_sql)["count(*)"].to_i
24
26
  result.save
25
27
  result
@@ -0,0 +1,54 @@
1
+ module Appstats
2
+ class ResultJob < ActiveRecord::Base
3
+ set_table_name "appstats_result_jobs"
4
+
5
+ attr_accessible :name, :frequency, :status, :query, :last_run_at
6
+
7
+ @@frequency_methods =
8
+
9
+ def should_run
10
+ return true if frequency == "once" && last_run_at.nil?
11
+ period = { "daily" => :beginning_of_day, "weekly" => :beginning_of_week, "monthly" => :beginning_of_month, "quarterly" => :beginning_of_quarter, "yearly" => :beginning_of_year }[frequency]
12
+ return false if period.nil?
13
+ return true if last_run_at.nil?
14
+ last_run_at.send(period) <= (Time.now.send(period) - 1.day).send(period)
15
+ end
16
+
17
+ def ==(o)
18
+ o.class == self.class && o.send(:state) == state
19
+ end
20
+ alias_method :eql?, :==
21
+
22
+ def self.run
23
+ count = 0
24
+ all = ResultJob.where("frequency <> 'once' or last_run_at IS NULL").all
25
+ if all.size == 0
26
+ Appstats.log(:info, "No result jobs to run.")
27
+ return count
28
+ end
29
+ Appstats.log(:info, "About to analyze #{all.size} result job(s).")
30
+ all.each do |job|
31
+ if job.should_run
32
+ Appstats.log(:info, " - Job #{job.name} run [ID #{job.id}, FREQUENCY #{job.frequency}, QUERY #{job.query}]")
33
+ query = Appstats::Query.new(:name => job.name, :result_type => "result_job", :query => job.query)
34
+ query.run
35
+ job.last_run_at = Time.now
36
+ job.save
37
+ count += 1
38
+ else
39
+ Appstats.log(:info, " - Job #{job.name} NOT run [ID #{job.id}, FREQUENCY #{job.frequency}, QUERY #{job.query}]")
40
+ end
41
+ end
42
+ Appstats.log(:info, "Ran #{count} query(ies).")
43
+ count
44
+ end
45
+
46
+ private
47
+
48
+ def state
49
+ [name, frequency, status, query, last_run_at]
50
+ end
51
+
52
+
53
+ end
54
+ end
@@ -1,3 +1,3 @@
1
1
  module Appstats
2
- VERSION = "0.10.0"
2
+ VERSION = "0.11.2"
3
3
  end
@@ -30,19 +30,27 @@ appstats_config = YAML::load(File.open(options[:config]))
30
30
  ActiveRecord::Base.establish_connection(appstats_config['database'])
31
31
  require File.join(File.dirname(__FILE__),"..","appstats")
32
32
 
33
+ last_processed_at = nil
33
34
  Appstats.log(:info,"Started Appstats Log Collector")
34
35
  while($running) do
36
+ unless Appstats::LogCollector.should_process(last_processed_at)
37
+ an_hour = 60*60
38
+ sleep an_hour
39
+ next
40
+ end
41
+ last_processed_at = Time.now
35
42
  ActiveRecord::Base.connection.reconnect!
36
43
  appstats_config["remote_servers"].each do |remote_server|
37
44
  Appstats::LogCollector.find_remote_files(remote_server,remote_server[:path],remote_server[:template])
38
45
  end
39
46
  Appstats::LogCollector.download_remote_files(appstats_config["remote_servers"])
40
47
  Appstats::LogCollector.process_local_files
48
+ Appstats::ResultJob.run
49
+
41
50
  Appstats::Action.update_actions
42
51
  Appstats::Host.update_hosts
43
52
  Appstats::ContextKey.update_context_keys
44
53
  Appstats::ContextValue.update_context_values
54
+ Appstats::LogCollector.remove_remote_files(appstats_config["remote_servers"])
45
55
  ActiveRecord::Base.connection.disconnect!
46
- a_day_in_seconds = 60*60*24
47
- sleep a_day_in_seconds
48
56
  end
@@ -82,12 +82,16 @@ module Appstats
82
82
  DateRange.parse(" yesterday ").should == DateRange.new(:from => EntryDate.new(:year => 2010, :month => 1, :day => 14), :to => nil, :format => :fixed_point )
83
83
  end
84
84
 
85
- describe "this (year|month|week|day)" do
85
+ describe "this (year|quarter|month|week|day)" do
86
86
 
87
87
  it "should understand this year" do
88
88
  DateRange.parse(" this year ").should == DateRange.new(:from => EntryDate.new(:year => 2010), :to => nil, :format => :fixed_point )
89
89
  end
90
90
 
91
+ it "should understand this quarter" do
92
+ DateRange.parse(" this quarter ").should == DateRange.new(:from => EntryDate.new(:year => 2010, :month => 1), :to => nil, :format => :inclusive )
93
+ end
94
+
91
95
  it "should understand this month" do
92
96
  DateRange.parse(" this month ").should == DateRange.new(:from => EntryDate.new(:year => 2010, :month => 1), :to => nil, :format => :fixed_point )
93
97
  end
@@ -102,12 +106,16 @@ module Appstats
102
106
 
103
107
  end
104
108
 
105
- describe "(last|previous) (year|month|week|day)" do
109
+ describe "(last|previous) (year|quarter|month|week|day)" do
106
110
 
107
111
  it "should understand last year" do
108
112
  DateRange.parse(" last year ").should == DateRange.new(:from => EntryDate.new(:year => 2009), :to => nil, :format => :fixed_point )
109
113
  end
110
114
 
115
+ it "should understand last quarter" do
116
+ DateRange.parse(" last quarter ").should == DateRange.new(:from => EntryDate.new(:year => 2009, :month => 10), :to => EntryDate.new(:year => 2009, :month => 12), :format => :inclusive )
117
+ end
118
+
111
119
  it "should understand last month" do
112
120
  DateRange.parse(" last month ").should == DateRange.new(:from => EntryDate.new(:year => 2009, :month => 12), :to => nil, :format => :fixed_point )
113
121
  end
@@ -138,7 +146,7 @@ module Appstats
138
146
 
139
147
  end
140
148
 
141
- describe "last X (year|month|week|day)s" do
149
+ describe "last X (year|quarter|month|week|day)s" do
142
150
 
143
151
  it "should understand last X years" do
144
152
  DateRange.parse(" last 1 year ").should == DateRange.new(:from => EntryDate.new(:year => 2010), :to => nil, :format => :inclusive )
@@ -146,6 +154,12 @@ module Appstats
146
154
  DateRange.parse(" last 3 years ").should == DateRange.new(:from => EntryDate.new(:year => 2008), :to => nil, :format => :inclusive )
147
155
  end
148
156
 
157
+ it "should understand last X quarters" do
158
+ DateRange.parse(" last 1 quarter ").should == DateRange.new(:from => EntryDate.new(:year => 2010, :month => 1), :to => nil, :format => :inclusive )
159
+ DateRange.parse(" last 2 quarters ").should == DateRange.new(:from => EntryDate.new(:year => 2009, :month => 10), :to => nil, :format => :inclusive )
160
+ DateRange.parse(" last 3 quarters ").should == DateRange.new(:from => EntryDate.new(:year => 2009, :month => 7), :to => nil, :format => :inclusive )
161
+ end
162
+
149
163
  it "should understand last X months" do
150
164
  DateRange.parse(" last 1 month ").should == DateRange.new(:from => EntryDate.new(:year => 2010, :month => 1), :to => nil, :format => :inclusive )
151
165
  DateRange.parse(" last 2 months ").should == DateRange.new(:from => EntryDate.new(:year => 2009, :month => 12), :to => nil, :format => :inclusive )
@@ -166,7 +180,7 @@ module Appstats
166
180
 
167
181
  end
168
182
 
169
- describe "previous X (year|month|week|day)s" do
183
+ describe "previous X (year|quarter|month|week|day)s" do
170
184
 
171
185
  it "should understand previous X years" do
172
186
  DateRange.parse(" previous 1 year ").should == DateRange.new(:from => EntryDate.new(:year => 2009), :to => EntryDate.new(:year => 2009), :format => :inclusive )
@@ -174,6 +188,12 @@ module Appstats
174
188
  DateRange.parse(" previous 3 years ").should == DateRange.new(:from => EntryDate.new(:year => 2007), :to => EntryDate.new(:year => 2009), :format => :inclusive )
175
189
  end
176
190
 
191
+ it "should understand previous Y quarters" do
192
+ DateRange.parse(" previous 1 quarter ").should == DateRange.new(:from => EntryDate.new(:year => 2009, :month => 10), :to => EntryDate.new(:year => 2009, :month => 12), :format => :inclusive )
193
+ DateRange.parse(" previous 2 quarters ").should == DateRange.new(:from => EntryDate.new(:year => 2009, :month => 7), :to => EntryDate.new(:year => 2009, :month => 12), :format => :inclusive )
194
+ DateRange.parse(" previous 3 quarters ").should == DateRange.new(:from => EntryDate.new(:year => 2009, :month => 4), :to => EntryDate.new(:year => 2009, :month => 12), :format => :inclusive )
195
+ end
196
+
177
197
  it "should understand previous Y months" do
178
198
  DateRange.parse(" previous 1 month ").should == DateRange.new(:from => EntryDate.new(:year => 2009, :month => 12), :to => EntryDate.new(:year => 2009, :month => 12), :format => :inclusive )
179
199
  DateRange.parse(" previous 2 months ").should == DateRange.new(:from => EntryDate.new(:year => 2009, :month => 11), :to => EntryDate.new(:year => 2009, :month => 12), :format => :inclusive )
@@ -75,6 +75,15 @@ module Appstats
75
75
  end
76
76
  end
77
77
 
78
+ describe "#end_of_quarter" do
79
+ it "should return the same 'level' data points" do
80
+ now = EntryDate.new(:year => 2010, :month => 1, :day => 5, :hour => 10)
81
+ expected = EntryDate.new(:year => 2010, :month => 3)
82
+ now.end_of_quarter.should == expected
83
+ end
84
+ end
85
+
86
+
78
87
  describe "#parse" do
79
88
 
80
89
  it "should deal with nil" do
@@ -118,6 +127,10 @@ module Appstats
118
127
  EntryDate.parse("last year").should == EntryDate.new(:year => 2009)
119
128
  end
120
129
 
130
+ it "should understand last quarter" do
131
+ EntryDate.parse("last quarter").should == EntryDate.new(:year => 2009, :month => 10)
132
+ end
133
+
121
134
  it "should understand last month" do
122
135
  EntryDate.parse("last month").should == EntryDate.new(:year => 2009, :month => 12)
123
136
  end
@@ -134,6 +147,10 @@ module Appstats
134
147
  EntryDate.parse("previous year").should == EntryDate.new(:year => 2009)
135
148
  end
136
149
 
150
+ it "should understand previous quarter" do
151
+ EntryDate.parse("previous quarter").should == EntryDate.new(:year => 2009, :month => 10)
152
+ end
153
+
137
154
  it "should understand previous month" do
138
155
  EntryDate.parse("previous month").should == EntryDate.new(:year => 2009, :month => 12)
139
156
  end
@@ -150,6 +167,10 @@ module Appstats
150
167
  EntryDate.parse("this year").should == EntryDate.new(:year => 2010)
151
168
  end
152
169
 
170
+ it "should understand this quarter" do
171
+ EntryDate.parse("this quarter").should == EntryDate.new(:year => 2010, :month => 1)
172
+ end
173
+
153
174
  it "should understand this month" do
154
175
  EntryDate.parse("this month").should == EntryDate.new(:year => 2010, :month => 1)
155
176
  end
@@ -168,6 +189,13 @@ module Appstats
168
189
  EntryDate.parse("last 3 years").should == EntryDate.new(:year => 2008)
169
190
  end
170
191
 
192
+ it "should understand last X quarters" do
193
+ EntryDate.parse("last 1 quarter").should == EntryDate.new(:year => 2010, :month => 1)
194
+ EntryDate.parse("last 2 quarters").should == EntryDate.new(:year => 2009, :month => 10)
195
+ EntryDate.parse("last 3 quarters").should == EntryDate.new(:year => 2009, :month => 7)
196
+ end
197
+
198
+
171
199
  it "should understand last X months" do
172
200
  EntryDate.parse("last 1 month").should == EntryDate.new(:year => 2010, :month => 1)
173
201
  EntryDate.parse("last 2 months").should == EntryDate.new(:year => 2009, :month => 12)
@@ -192,6 +220,12 @@ module Appstats
192
220
  EntryDate.parse("previous 3 years").should == EntryDate.new(:year => 2007)
193
221
  end
194
222
 
223
+ it "should understand previous X quarters" do
224
+ EntryDate.parse("previous 1 quarter").should == EntryDate.new(:year => 2009, :month => 10)
225
+ EntryDate.parse("previous 2 quarters").should == EntryDate.new(:year => 2009, :month => 7)
226
+ EntryDate.parse("previous 3 quarters").should == EntryDate.new(:year => 2009, :month => 4)
227
+ end
228
+
195
229
  it "should understand previous X months" do
196
230
  EntryDate.parse("previous 1 month").should == EntryDate.new(:year => 2009, :month => 12)
197
231
  EntryDate.parse("previous 2 months").should == EntryDate.new(:year => 2009, :month => 11)