appstats 0.1.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/db/migrations/20110207213514_create_appstats_actions.rb +14 -0
- data/db/migrations/20110208210921_align_entry_time_names.rb +15 -0
- data/db/schema.rb +12 -4
- data/lib/appstats.rb +4 -0
- data/lib/appstats/action.rb +19 -0
- data/lib/appstats/date_range.rb +159 -0
- data/lib/appstats/entry.rb +4 -4
- data/lib/appstats/entry_date.rb +156 -0
- data/lib/appstats/query.rb +71 -0
- data/lib/appstats/version.rb +1 -1
- data/lib/daemons/appstats_log_collector.rb +2 -0
- data/spec/action_spec.rb +57 -0
- data/spec/date_range_spec.rb +286 -0
- data/spec/entry_date_spec.rb +221 -0
- data/spec/entry_spec.rb +29 -29
- data/spec/logger_spec.rb +16 -16
- data/spec/query_spec.rb +137 -0
- metadata +18 -4
data/Gemfile.lock
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateAppstatsActions < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :appstats_actions do |t|
|
4
|
+
t.string :name
|
5
|
+
t.string :plural_name
|
6
|
+
t.string :status
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.down
|
12
|
+
drop_table :appstats_actions
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class AlignEntryTimeNames < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
remove_index :appstats_entries, :name => "index_entries_by_minute"
|
4
|
+
rename_column :appstats_entries, :minute, :min
|
5
|
+
rename_column :appstats_entries, :second, :sec
|
6
|
+
add_index :appstats_entries, [:year,:month,:day,:hour,:min], :name => "index_entries_by_minute"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.down
|
10
|
+
remove_index :appstats_entries, :name => "index_entries_by_minute"
|
11
|
+
rename_column :appstats_entries, :min, :minute
|
12
|
+
rename_column :appstats_entries, :sec, :second
|
13
|
+
add_index :appstats_entries, [:year,:month,:day,:hour,:minute], :name => "index_entries_by_minute"
|
14
|
+
end
|
15
|
+
end
|
data/db/schema.rb
CHANGED
@@ -10,7 +10,15 @@
|
|
10
10
|
#
|
11
11
|
# It's strongly recommended to check this file into your version control system.
|
12
12
|
|
13
|
-
ActiveRecord::Schema.define(:version =>
|
13
|
+
ActiveRecord::Schema.define(:version => 20110208210921) do
|
14
|
+
|
15
|
+
create_table "appstats_actions", :force => true do |t|
|
16
|
+
t.string "name"
|
17
|
+
t.string "plural_name"
|
18
|
+
t.string "status"
|
19
|
+
t.datetime "created_at"
|
20
|
+
t.datetime "updated_at"
|
21
|
+
end
|
14
22
|
|
15
23
|
create_table "appstats_contexts", :force => true do |t|
|
16
24
|
t.string "context_key"
|
@@ -37,12 +45,12 @@ ActiveRecord::Schema.define(:version => 20110207200431) do
|
|
37
45
|
t.integer "month"
|
38
46
|
t.integer "day"
|
39
47
|
t.integer "hour"
|
40
|
-
t.integer "
|
41
|
-
t.integer "
|
48
|
+
t.integer "min"
|
49
|
+
t.integer "sec"
|
42
50
|
end
|
43
51
|
|
44
52
|
add_index "appstats_entries", ["action"], :name => "index_appstats_entries_on_action"
|
45
|
-
add_index "appstats_entries", ["year", "month", "day", "hour", "
|
53
|
+
add_index "appstats_entries", ["year", "month", "day", "hour", "min"], :name => "index_entries_by_minute"
|
46
54
|
add_index "appstats_entries", ["year", "month", "day", "hour"], :name => "index_entries_by_hour"
|
47
55
|
add_index "appstats_entries", ["year", "month", "day"], :name => "index_entries_by_day"
|
48
56
|
add_index "appstats_entries", ["year", "month"], :name => "index_entries_by_month"
|
data/lib/appstats.rb
CHANGED
@@ -2,10 +2,14 @@ require 'rubygems'
|
|
2
2
|
require 'active_record'
|
3
3
|
require "#{File.dirname(__FILE__)}/appstats/code_injections"
|
4
4
|
require "#{File.dirname(__FILE__)}/appstats/entry"
|
5
|
+
require "#{File.dirname(__FILE__)}/appstats/entry_date"
|
6
|
+
require "#{File.dirname(__FILE__)}/appstats/date_range"
|
7
|
+
require "#{File.dirname(__FILE__)}/appstats/action"
|
5
8
|
require "#{File.dirname(__FILE__)}/appstats/context"
|
6
9
|
require "#{File.dirname(__FILE__)}/appstats/tasks"
|
7
10
|
require "#{File.dirname(__FILE__)}/appstats/logger"
|
8
11
|
require "#{File.dirname(__FILE__)}/appstats/log_collector"
|
12
|
+
require "#{File.dirname(__FILE__)}/appstats/query"
|
9
13
|
|
10
14
|
# required in the appstats.gemspec
|
11
15
|
# require "#{File.dirname(__FILE__)}/appstats/version"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
module Appstats
|
3
|
+
class Action < ActiveRecord::Base
|
4
|
+
set_table_name "appstats_actions"
|
5
|
+
|
6
|
+
attr_accessible :name, :plural_name, :status
|
7
|
+
|
8
|
+
def self.update_actions
|
9
|
+
sql = "select distinct(action) from appstats_entries where action not in (select name from appstats_actions)"
|
10
|
+
count = 0
|
11
|
+
ActiveRecord::Base.connection.execute(sql).each do |row|
|
12
|
+
Appstats::Action.create(:name => row[0], :plural_name => row[0].pluralize, :status => 'derived')
|
13
|
+
count += 1
|
14
|
+
end
|
15
|
+
count
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
|
2
|
+
module Appstats
|
3
|
+
class DateRange
|
4
|
+
|
5
|
+
attr_accessor :from, :to, :format
|
6
|
+
|
7
|
+
def initialize(data = {})
|
8
|
+
@from = data[:from]
|
9
|
+
@to = data[:to]
|
10
|
+
@format = data[:format] || :inclusive
|
11
|
+
end
|
12
|
+
|
13
|
+
def from_to_s
|
14
|
+
return nil if @from.nil?
|
15
|
+
mode = @format == :inclusive ? :start : :end
|
16
|
+
@from.to_time(mode).strftime('%Y-%m-%d %H:%M:%S')
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_to_s
|
20
|
+
return nil if @to.nil?
|
21
|
+
mode = @format == :exclusive ? :start : :end
|
22
|
+
@to.to_time(mode).strftime('%Y-%m-%d %H:%M:%S')
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_sql
|
26
|
+
return "1=1" if @from.nil? && @to.nil?
|
27
|
+
|
28
|
+
if !@from.nil? && @to.nil?
|
29
|
+
return case @format
|
30
|
+
when :inclusive then
|
31
|
+
"occurred_at >= '#{from_to_s}'"
|
32
|
+
when :exclusive then
|
33
|
+
"occurred_at > '#{from_to_s}'"
|
34
|
+
when :fixed_point then
|
35
|
+
answer = "("
|
36
|
+
[:year,:month,:day,:hour,:min,:sec].each do |t|
|
37
|
+
next if from.send(t).nil?
|
38
|
+
answer += " and " unless answer.size == 1
|
39
|
+
answer += "#{t}=#{from.send(t)}"
|
40
|
+
end
|
41
|
+
answer += ")"
|
42
|
+
answer
|
43
|
+
end
|
44
|
+
elsif @from.nil? && !@to.nil?
|
45
|
+
return case @format
|
46
|
+
when :inclusive then "occurred_at <= '#{to_to_s}'"
|
47
|
+
when :exclusive then "occurred_at < '#{to_to_s}'"
|
48
|
+
else "1=1"
|
49
|
+
end
|
50
|
+
else
|
51
|
+
return case @format
|
52
|
+
when :inclusive then "(occurred_at >= '#{from_to_s}' and occurred_at <= '#{to_to_s}')"
|
53
|
+
when :exclusive then "(occurred_at > '#{from_to_s}' and occurred_at < '#{to_to_s}')"
|
54
|
+
else "1=1"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.parse(raw_input)
|
62
|
+
range = DateRange.new
|
63
|
+
return range if raw_input.nil? || raw_input == ''
|
64
|
+
input = raw_input.strip
|
65
|
+
|
66
|
+
m = input.match(/^today|yesterday|YTD|ytd$/)
|
67
|
+
unless m.nil?
|
68
|
+
range.from = EntryDate.parse(input)
|
69
|
+
range.format = :fixed_point
|
70
|
+
end
|
71
|
+
|
72
|
+
m = input.match(/^between\s*(.*)\s*and\s*(.*)$/)
|
73
|
+
unless m.nil?
|
74
|
+
range.from = EntryDate.parse(m[1])
|
75
|
+
range.to = EntryDate.parse(m[2])
|
76
|
+
return range
|
77
|
+
end
|
78
|
+
|
79
|
+
m = input.match(/^(in|on)\s*(.*)$/)
|
80
|
+
unless m.nil?
|
81
|
+
range.from = EntryDate.parse(m[2])
|
82
|
+
range.format = :fixed_point
|
83
|
+
return range
|
84
|
+
end
|
85
|
+
|
86
|
+
m = input.match(/^before\s*(.*)$/)
|
87
|
+
unless m.nil?
|
88
|
+
range.to = EntryDate.parse(m[1])
|
89
|
+
range.format = :exclusive
|
90
|
+
return range
|
91
|
+
end
|
92
|
+
|
93
|
+
m = input.match(/^after\s*(.*)$/)
|
94
|
+
unless m.nil?
|
95
|
+
range.from = EntryDate.parse(m[1])
|
96
|
+
range.format = :exclusive
|
97
|
+
return range
|
98
|
+
end
|
99
|
+
|
100
|
+
m = input.match(/^since\s*(.*)$/)
|
101
|
+
unless m.nil?
|
102
|
+
range.from = EntryDate.parse(m[1])
|
103
|
+
range.format = :inclusive
|
104
|
+
return range
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
m = input.match(/^this\s*(year|month|week|day)$/)
|
109
|
+
unless m.nil?
|
110
|
+
range.from = EntryDate.parse(input)
|
111
|
+
range.format = m[1] == "week" ? :inclusive : :fixed_point
|
112
|
+
return range
|
113
|
+
end
|
114
|
+
|
115
|
+
m = input.match(/^(last|previous)\s*(year|month|week|day)$/)
|
116
|
+
unless m.nil?
|
117
|
+
range.from = EntryDate.parse(input)
|
118
|
+
if m[2] == "week"
|
119
|
+
range.to = range.from.end_of_week
|
120
|
+
range.format = :inclusive
|
121
|
+
else
|
122
|
+
range.format = :fixed_point
|
123
|
+
end
|
124
|
+
return range
|
125
|
+
end
|
126
|
+
|
127
|
+
m = input.match(/^last\s*(.+)\s*(year|years|month|months|week|weeks|day|days)$/)
|
128
|
+
unless m.nil?
|
129
|
+
range.from = EntryDate.parse(input)
|
130
|
+
range.format = :inclusive
|
131
|
+
return range
|
132
|
+
end
|
133
|
+
|
134
|
+
m = input.match(/^previous\s*(.+)\s*(year|month|week|day)s?$/)
|
135
|
+
unless m.nil?
|
136
|
+
range.from = EntryDate.parse(input)
|
137
|
+
to = EntryDate.parse("last #{m[2]}")
|
138
|
+
to = to.end_of_week if m[2] == "week"
|
139
|
+
range.to = to
|
140
|
+
range.format = :inclusive
|
141
|
+
return range
|
142
|
+
end
|
143
|
+
|
144
|
+
range
|
145
|
+
end
|
146
|
+
|
147
|
+
def ==(o)
|
148
|
+
o.class == self.class && o.send(:state) == state
|
149
|
+
end
|
150
|
+
alias_method :eql?, :==
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def state
|
155
|
+
[@from, @to, @format]
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
data/lib/appstats/entry.rb
CHANGED
@@ -17,15 +17,15 @@ module Appstats
|
|
17
17
|
self[:month] = nil
|
18
18
|
self[:day] = nil
|
19
19
|
self[:hour] = nil
|
20
|
-
self[:
|
21
|
-
self[:
|
20
|
+
self[:min] = nil
|
21
|
+
self[:sec] = nil
|
22
22
|
else
|
23
23
|
self[:year] = value.year
|
24
24
|
self[:month] = value.month
|
25
25
|
self[:day] = value.day
|
26
26
|
self[:hour] = value.hour
|
27
|
-
self[:
|
28
|
-
self[:
|
27
|
+
self[:min] = value.min
|
28
|
+
self[:sec] = value.sec
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -0,0 +1,156 @@
|
|
1
|
+
|
2
|
+
module Appstats
|
3
|
+
class EntryDate
|
4
|
+
|
5
|
+
attr_accessor :year, :month, :day, :hour, :min, :sec
|
6
|
+
|
7
|
+
def initialize(data = {})
|
8
|
+
@year = data[:year]
|
9
|
+
@month = data[:month]
|
10
|
+
@day = data[:day]
|
11
|
+
@hour = data[:hour]
|
12
|
+
@min = data[:min]
|
13
|
+
@sec = data[:sec]
|
14
|
+
end
|
15
|
+
|
16
|
+
def end_of_week
|
17
|
+
week = self.dup
|
18
|
+
t = to_time.end_of_week
|
19
|
+
week.year = t.year
|
20
|
+
week.month = t.month
|
21
|
+
week.day = t.day
|
22
|
+
week
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_time(mode = :start)
|
26
|
+
return Time.now if @year.nil?
|
27
|
+
t = Time.parse("#{@year}-#{@month||'01'}-#{@day||'01'} #{@hour||'00'}:#{@min||'00'}:#{@sec||'00'}")
|
28
|
+
|
29
|
+
if mode == :end
|
30
|
+
t = t.end_of_year if @month.nil?
|
31
|
+
t = t.end_of_month if @day.nil?
|
32
|
+
t = t.end_of_day if @hour.nil?
|
33
|
+
end
|
34
|
+
t
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
s = ""
|
39
|
+
return s if @year.nil?
|
40
|
+
s += "#{@year}"
|
41
|
+
|
42
|
+
return s if @month.nil?
|
43
|
+
s += "-#{@month.to_s.rjust(2,'0')}"
|
44
|
+
|
45
|
+
return s if @day.nil?
|
46
|
+
s += "-#{@day.to_s.rjust(2,'0')}"
|
47
|
+
|
48
|
+
return s if @hour.nil?
|
49
|
+
s += " #{@hour.to_s.rjust(2,'0')}"
|
50
|
+
|
51
|
+
return s if @min.nil?
|
52
|
+
s += ":#{@min.to_s.rjust(2,'0')}"
|
53
|
+
|
54
|
+
return s if @sec.nil?
|
55
|
+
s += ":#{@sec.to_s.rjust(2,'0')}"
|
56
|
+
|
57
|
+
s
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.parse(raw_input)
|
61
|
+
date = EntryDate.new
|
62
|
+
return date if raw_input.nil? || raw_input == ''
|
63
|
+
input = raw_input.strip
|
64
|
+
|
65
|
+
if input.match(/^\d*$/) # year
|
66
|
+
date.year = input.to_i
|
67
|
+
return date
|
68
|
+
end
|
69
|
+
|
70
|
+
t = Time.now
|
71
|
+
t_parts = nil
|
72
|
+
|
73
|
+
if input.match(/^YTD|ytd$/)
|
74
|
+
t_parts = [:year]
|
75
|
+
elsif input.match(/^today$/)
|
76
|
+
t_parts = [:year,:month,:day]
|
77
|
+
elsif input.match(/^yesterday$/)
|
78
|
+
t -= 1.day
|
79
|
+
t_parts = [:year,:month,:day]
|
80
|
+
elsif input.match(/^this year$/)
|
81
|
+
t_parts = [:year]
|
82
|
+
elsif input.match(/^this month$/)
|
83
|
+
t_parts = [:year,:month]
|
84
|
+
elsif input.match(/^this week$/)
|
85
|
+
t = t.beginning_of_week
|
86
|
+
t_parts = [:year,:month,:day]
|
87
|
+
elsif input.match(/^this day$/)
|
88
|
+
t_parts = [:year,:month,:day]
|
89
|
+
elsif input.match(/^(.*),[^\d]*(\d*)$/) # month, year
|
90
|
+
t = Time.parse(input)
|
91
|
+
t_parts = [:year,:month]
|
92
|
+
elsif input.match(/^(\d*)-(\d*)-(\d*)$/) # YYYY-mm-dd
|
93
|
+
t = Time.parse(input)
|
94
|
+
t_parts = [:year,:month,:day]
|
95
|
+
end
|
96
|
+
|
97
|
+
m = input.match(/^(last|previous)\s*(\d*)\s*years?$/)
|
98
|
+
if m
|
99
|
+
t -= last_date_offset(m).year
|
100
|
+
t_parts = [:year]
|
101
|
+
end
|
102
|
+
|
103
|
+
m = input.match(/^(last|previous)\s*(\d*)\s*months?$/)
|
104
|
+
if m
|
105
|
+
t -= last_date_offset(m).month
|
106
|
+
t_parts = [:year,:month]
|
107
|
+
end
|
108
|
+
|
109
|
+
m = input.match(/^(last|previous)\s*(\d*)\s*weeks?$/)
|
110
|
+
if m
|
111
|
+
t = (t - last_date_offset(m).week).beginning_of_week
|
112
|
+
t_parts = [:year,:month,:day]
|
113
|
+
end
|
114
|
+
|
115
|
+
m = input.match(/^(last|previous)\s*(\d*)\s*days?$/)
|
116
|
+
if m
|
117
|
+
t -= last_date_offset(m).day
|
118
|
+
t_parts = [:year,:month,:day]
|
119
|
+
end
|
120
|
+
|
121
|
+
unless t_parts.nil?
|
122
|
+
t_parts.each do |label|
|
123
|
+
date.send("#{label}=",t.send(label))
|
124
|
+
end
|
125
|
+
return date
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
begin
|
130
|
+
t = Time.parse(input)
|
131
|
+
return EntryDate.new(:year => t.year, :month => t.month, :day => t.day, :hour => t.hour, :min => t.min, :sec => t.sec)
|
132
|
+
rescue
|
133
|
+
return EntryDate.new
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def ==(o)
|
138
|
+
o.class == self.class && o.send(:state) == state
|
139
|
+
end
|
140
|
+
alias_method :eql?, :==
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def state
|
145
|
+
[@year, @month, @day, @hour, @min, @sec]
|
146
|
+
end
|
147
|
+
|
148
|
+
# (last|previous) (\d*)
|
149
|
+
def self.last_date_offset(match)
|
150
|
+
offset = match[1] == "last" ? -1 : 0
|
151
|
+
amount = match[2] == "" ? 1 : match[2].to_i + offset
|
152
|
+
amount
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|