periodic_calculations 0.0.2 → 0.0.3
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/README.md +11 -1
- data/lib/periodic_calculations/base.rb +6 -4
- data/lib/periodic_calculations/query.rb +2 -1
- data/lib/periodic_calculations/version.rb +1 -1
- data/spec/model_spec.rb +7 -0
- data/spec/performance_matchers.rb +17 -0
- data/spec/performance_spec.rb +40 -0
- data/spec/periodic_calculations/query_spec.rb +2 -1
- data/spec/spec_helper.rb +8 -0
- metadata +6 -2
data/README.md
CHANGED
@@ -5,4 +5,14 @@
|
|
5
5
|
[](https://coveralls.io/r/polmiro/periodic_calculations)
|
6
6
|
[](http://badge.fury.io/rb/periodic_calculations)
|
7
7
|
|
8
|
-
Periodic Calculations gem allows you to retrieve periodic results of aggregates that can be accumulated over time with PostgreSQL.
|
8
|
+
Periodic Calculations gem allows you to retrieve periodic results of aggregates that can be accumulated over time with PostgreSQL. The results are returned in real time (there are no scheduled precalculations).
|
9
|
+
|
10
|
+
The returned data is ready to be displayed in a graph, for example, using the [jQuery Flot](http://www.flotcharts.org/) library.
|
11
|
+
|
12
|
+
## Demo ##
|
13
|
+
|
14
|
+
Please check out the [demo](http://periodic-calculations-demo.herokuapp.com) to see it in action.
|
15
|
+
|
16
|
+
## How does it work ##
|
17
|
+
|
18
|
+
It takes advantage of the [window_functions](http://www.postgresql.org/docs/9.1/static/tutorial-window.html) to be able to generate accumulated metrics over time.
|
@@ -2,10 +2,11 @@ module PeriodicCalculations
|
|
2
2
|
module Base
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
|
-
# TODO:
|
6
|
-
# * Rails 4 compatible only right now (scoped vs all)
|
7
|
-
|
8
5
|
module ClassMethods
|
6
|
+
def periodic_count_all(*args)
|
7
|
+
periodic_calculation(:count, "*", *args)
|
8
|
+
end
|
9
|
+
|
9
10
|
def periodic_count(*args)
|
10
11
|
periodic_calculation(:count, *args)
|
11
12
|
end
|
@@ -28,7 +29,8 @@ module PeriodicCalculations
|
|
28
29
|
|
29
30
|
def periodic_calculation(operation, column_name, window_start, window_end, options = {})
|
30
31
|
query_options = QueryOptions.new(operation, column_name, window_start, window_end, options)
|
31
|
-
|
32
|
+
current_scope = Rails::VERSION::MAJOR >= 4 ? all : scoped
|
33
|
+
query = Query.new(current_scope, query_options)
|
32
34
|
LazyQuery.new(query)
|
33
35
|
end
|
34
36
|
end
|
@@ -16,6 +16,7 @@ module PeriodicCalculations
|
|
16
16
|
@timestamp_column = query_options.timestamp_column
|
17
17
|
@operation = query_options.operation.upcase
|
18
18
|
@window_function = query_options.cumulative ? "ORDER" : "PARTITION"
|
19
|
+
@inside_operation = query_options.operation == :count ? "SUM" : query_options.operation.upcase
|
19
20
|
@binds = {
|
20
21
|
:unit => query_options.interval_unit,
|
21
22
|
:interval => "1 #{query_options.interval_unit.upcase}",
|
@@ -82,7 +83,7 @@ module PeriodicCalculations
|
|
82
83
|
-- running window function calculate results and fill up gaps
|
83
84
|
, results AS (
|
84
85
|
SELECT DISTINCT frame,
|
85
|
-
#{@
|
86
|
+
#{@inside_operation}(result) OVER (#{@window_function} BY frame) AS result
|
86
87
|
FROM (
|
87
88
|
SELECT frame, result FROM preprocessed_results
|
88
89
|
UNION ALL
|
data/spec/model_spec.rb
CHANGED
@@ -9,6 +9,13 @@ describe "Model" do
|
|
9
9
|
klass.should respond_to(:periodic_calculation)
|
10
10
|
end
|
11
11
|
|
12
|
+
describe ".periodic_count_all" do
|
13
|
+
it "calculates a periodic count operation for all rows" do
|
14
|
+
klass.should_receive(:periodic_calculation).with(:count, "*", :args).once.and_return(true)
|
15
|
+
klass.periodic_count_all(:args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
12
19
|
describe ".periodic_count" do
|
13
20
|
it "calculates a periodic count operation" do
|
14
21
|
klass.should_receive(:periodic_calculation).with(:count, :args).once.and_return(true)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
RSpec::Matchers.define :perform_in do |goal|
|
2
|
+
match do |actual|
|
3
|
+
@time = Benchmark.realtime do
|
4
|
+
actual.call
|
5
|
+
end
|
6
|
+
|
7
|
+
@time < goal
|
8
|
+
end
|
9
|
+
|
10
|
+
failure_message_for_should do |actual|
|
11
|
+
"It took too long. Goal: #{goal}, Benchmark: #{@time}"
|
12
|
+
end
|
13
|
+
|
14
|
+
failure_message_for_should_not do |actual|
|
15
|
+
"It did not take long enough. Goal: #{goal}, Benchmark: #{@time}"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
describe "Performance tests", :performance => true do
|
2
|
+
PERIODS = 300
|
3
|
+
DATA_POINTS_PER_INTERVAL = 3000
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
ActiveRecord::Base.connection.execute("DELETE FROM activities;")
|
7
|
+
|
8
|
+
@time = Time.now
|
9
|
+
PERIODS.times do |days|
|
10
|
+
inserts = []
|
11
|
+
|
12
|
+
DATA_POINTS_PER_INTERVAL.times do |quantity|
|
13
|
+
quantity = 4000 + (quantity * 100)
|
14
|
+
created_at = @time - days.days
|
15
|
+
inserts << "(#{quantity}, '#{created_at.utc}')"
|
16
|
+
end
|
17
|
+
|
18
|
+
ActiveRecord::Base.connection.execute(<<-SQL)
|
19
|
+
INSERT INTO activities(quantity, created_at) VALUES #{inserts.join(", ")}
|
20
|
+
SQL
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
after :all do
|
25
|
+
ActiveRecord::Base.connection.execute("DELETE FROM activities;")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should be performant with non cumulative queries" do
|
29
|
+
expect do
|
30
|
+
Activity.periodic_sum(:quantity, @time - PERIODS.days, @time).result
|
31
|
+
end.to perform_in(1)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should be performant cumulative queries" do
|
35
|
+
expect do
|
36
|
+
Activity.periodic_sum(:quantity, @time - PERIODS.days, @time, :cumulative => true).result
|
37
|
+
end.to perform_in(1)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -124,12 +124,13 @@ describe PeriodicCalculations::Query do
|
|
124
124
|
|
125
125
|
# outside interval matters
|
126
126
|
Activity.create(:created_at => time - 10.day)
|
127
|
+
Activity.create(:created_at => time - 10.day)
|
127
128
|
|
128
129
|
Activity.create(:created_at => time - 1.day)
|
129
130
|
Activity.create(:created_at => time)
|
130
131
|
Activity.create(:created_at => time + 1.day)
|
131
132
|
|
132
|
-
execute(scope, operation, column_name, start_time, end_time, options).map(&:last).should == [
|
133
|
+
execute(scope, operation, column_name, start_time, end_time, options).map(&:last).should == [3, 4, 5]
|
133
134
|
end
|
134
135
|
end
|
135
136
|
|
data/spec/spec_helper.rb
CHANGED
@@ -4,3 +4,11 @@ Coveralls.wear!
|
|
4
4
|
ENV["RAILS_ENV"] = "test"
|
5
5
|
require File.expand_path("../dummy/config/environment", __FILE__)
|
6
6
|
require 'rspec/rails'
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.filter_run_excluding :performance => true
|
10
|
+
config.before(:all, :performance => true) do
|
11
|
+
require 'benchmark'
|
12
|
+
require 'performance_matchers'
|
13
|
+
end
|
14
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: periodic_calculations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-12-
|
12
|
+
date: 2013-12-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -154,6 +154,8 @@ files:
|
|
154
154
|
- spec/dummy/public/500.html
|
155
155
|
- spec/dummy/public/favicon.ico
|
156
156
|
- spec/model_spec.rb
|
157
|
+
- spec/performance_matchers.rb
|
158
|
+
- spec/performance_spec.rb
|
157
159
|
- spec/periodic_calculations/lazy_query_spec.rb
|
158
160
|
- spec/periodic_calculations/query_options_spec.rb
|
159
161
|
- spec/periodic_calculations/query_spec.rb
|
@@ -223,6 +225,8 @@ test_files:
|
|
223
225
|
- spec/dummy/public/500.html
|
224
226
|
- spec/dummy/public/favicon.ico
|
225
227
|
- spec/model_spec.rb
|
228
|
+
- spec/performance_matchers.rb
|
229
|
+
- spec/performance_spec.rb
|
226
230
|
- spec/periodic_calculations/lazy_query_spec.rb
|
227
231
|
- spec/periodic_calculations/query_options_spec.rb
|
228
232
|
- spec/periodic_calculations/query_spec.rb
|