appstats 0.1.0 → 0.3.1
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 +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
|