active_metric 2.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +5 -0
  3. data/Rakefile +37 -0
  4. data/lib/active_metric.rb +38 -0
  5. data/lib/active_metric/axis.rb +11 -0
  6. data/lib/active_metric/behavior/calculates_derivative.rb +18 -0
  7. data/lib/active_metric/behavior/graph_calculation.rb +78 -0
  8. data/lib/active_metric/calculators/reservoir.rb +81 -0
  9. data/lib/active_metric/calculators/standard_deviator.rb +39 -0
  10. data/lib/active_metric/config/initializers/inflections.rb +4 -0
  11. data/lib/active_metric/graph_view_model.rb +60 -0
  12. data/lib/active_metric/measurement.rb +13 -0
  13. data/lib/active_metric/point_series_data.rb +17 -0
  14. data/lib/active_metric/report.rb +57 -0
  15. data/lib/active_metric/report_view_model.rb +106 -0
  16. data/lib/active_metric/sample.rb +148 -0
  17. data/lib/active_metric/series_data.rb +26 -0
  18. data/lib/active_metric/stat.rb +72 -0
  19. data/lib/active_metric/stat_definition.rb +20 -0
  20. data/lib/active_metric/statistics/defaults.rb +157 -0
  21. data/lib/active_metric/statistics/standard_deviation.rb +43 -0
  22. data/lib/active_metric/subject.rb +117 -0
  23. data/lib/active_metric/version.rb +3 -0
  24. data/test/active_metric_test.rb +30 -0
  25. data/test/axis_test.rb +22 -0
  26. data/test/behavior_tests/calculates_derivative_test.rb +35 -0
  27. data/test/behavior_tests/graph_calculation_test.rb +68 -0
  28. data/test/config/mongoid.yml +13 -0
  29. data/test/dummy/db/test.sqlite3 +0 -0
  30. data/test/dummy/log/test.log +18597 -0
  31. data/test/graph_view_model_test.rb +92 -0
  32. data/test/integration_test.rb +149 -0
  33. data/test/measurement_test.rb +45 -0
  34. data/test/mongoid_test.rb +24 -0
  35. data/test/point_series_data_test.rb +27 -0
  36. data/test/report_test.rb +73 -0
  37. data/test/report_view_model_test.rb +94 -0
  38. data/test/reservoir_test.rb +67 -0
  39. data/test/sample_test.rb +142 -0
  40. data/test/series_data_test.rb +20 -0
  41. data/test/standard_deviator_test.rb +45 -0
  42. data/test/stat_test.rb +222 -0
  43. data/test/subject_test.rb +22 -0
  44. data/test/test_helper.rb +76 -0
  45. metadata +123 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2011 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = ActiveMetric
2
+
3
+ ActiveMetric is a library to generate reports.
4
+
5
+ By defining a report, subjects, samples and view models you can pass measurements into your subjects and generate graph data.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ActiveMetric'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task :default => :test
@@ -0,0 +1,38 @@
1
+ require 'mongoid'
2
+ require 'active_support/test_case'
3
+
4
+ require 'active_metric/behavior/graph_calculation'
5
+ require 'active_metric/behavior/calculates_derivative'
6
+ require 'active_metric/calculators/standard_deviator'
7
+
8
+ require 'active_metric/subject'
9
+ require 'active_metric/measurement'
10
+ require 'active_metric/sample'
11
+ require 'active_metric/stat'
12
+ require 'active_metric/statistics/defaults'
13
+ require 'active_metric/statistics/standard_deviation'
14
+ require 'active_metric/report'
15
+ require 'active_metric/calculators/reservoir'
16
+ require 'active_metric/report_view_model'
17
+ require 'active_metric/graph_view_model'
18
+ require 'active_metric/series_data'
19
+ require 'active_metric/point_series_data'
20
+ require 'active_metric/axis'
21
+ require 'active_metric/stat_definition'
22
+
23
+ module ActiveMetric
24
+ CONFIG_PATH = File.join(File.dirname(__FILE__),"active_metric/config")
25
+
26
+ def self.logger
27
+ @logger ||= Logger.new(STDOUT)
28
+ end
29
+
30
+ def self.logger=(new_logger)
31
+ @logger = new_logger
32
+ end
33
+
34
+ end
35
+
36
+ Dir.glob("#{File.dirname(__FILE__)}/active_metric/config/initializers/*").each do |initializer|
37
+ require initializer
38
+ end
@@ -0,0 +1,11 @@
1
+ module ActiveMetric
2
+ class Axis
3
+ include Mongoid::Document
4
+
5
+ embedded_in :graph_view_model, :class_name => "ActiveMetric::GraphViewModel"
6
+
7
+ field :label, default: "units", type: String
8
+ field :min
9
+ field :index, default: 0, type: Integer
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ module ActiveMetric
2
+ module CalculatesDerivative
3
+
4
+ def derivative_from_seed_measurement(first, last)
5
+ duration_from_seed_measurement = calculable.duration_from_previous_sample_in_seconds
6
+ calculate_derivative(first, last, duration_from_seed_measurement)
7
+ end
8
+
9
+ def calculate_derivative(first, last, duration)
10
+ if duration > 0
11
+ (last - first).to_f / duration
12
+ else
13
+ 0
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,78 @@
1
+ module ActiveMetric
2
+ module GraphCalculation
3
+ #REQUIRES SUMMARY AND INTERVAL SAMPLES AND SERIES DATA
4
+ MONGO_MAX_LIMIT = (1 << 31) - 1
5
+
6
+ def self.included(klass)
7
+ klass.has_one :graph_view_model,
8
+ :class_name => "ActiveMetric::GraphViewModel", :autosave => true
9
+ klass.after_initialize do
10
+ self.graph_view_model ||= initialize_graph_view_model
11
+ end
12
+ end
13
+
14
+ def series
15
+ return nil unless attributes["series_data"]
16
+ attributes["series_data"].values
17
+ end
18
+
19
+ def has_graph_data
20
+ true
21
+ end
22
+
23
+ def graph_view_model_starting_at(index)
24
+ GraphViewModel.where(subject_id: id).slice("series_data.data" => [index, MONGO_MAX_LIMIT]).first
25
+ end
26
+
27
+ def update_graph_model(interval_sample)
28
+ pop_necessary_samples(interval_sample)
29
+ interval_sample.stats.each do |stat|
30
+ series = graph_view_model.series_for(stat.access_name.to_s)
31
+ series.push_data([time(interval_sample.timestamp), stat.value]) if series && interval_sample.timestamp && start_time
32
+ end
33
+
34
+ self.save!
35
+ end
36
+
37
+ def pop_necessary_samples(interval_sample)
38
+ data_to_pop = calculate_data_to_pop(interval_sample)
39
+ pop_from_series(data_to_pop) if data_to_pop > 0
40
+ end
41
+
42
+ def calculate_data_to_pop(interval_sample)
43
+ incoming_index = interval_sample.sample_index
44
+ next_index = graph_view_model.size
45
+ next_index - incoming_index
46
+ end
47
+
48
+ def pop_from_series(data_to_pop)
49
+ graph_view_model.series_data.each do |series|
50
+ series.pop_data(data_to_pop)
51
+ end
52
+ end
53
+
54
+ def initialize_graph_view_model
55
+ if self.class.sample_type
56
+ axises_defined = self.class.sample_type.axises_defined
57
+ stats_defined = self.class.sample_type.stats_defined
58
+ else
59
+ axises_defined = []
60
+ stats_defined = []
61
+ end
62
+ GraphViewModel.create_from_meta_data(axises_defined, stats_defined, name: name)
63
+ end
64
+
65
+ def time(sample_time)
66
+ ((sample_time - start_time)).to_i
67
+ end
68
+
69
+ def start_time
70
+ @start_time ||= summary.start_time
71
+ end
72
+
73
+ def debug(message)
74
+ ActiveMetric.logger.error "DEBUGAM #{self.name} #{message}"
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,81 @@
1
+ module ActiveMetric
2
+ class Reservoir
3
+
4
+ attr_reader :size
5
+ attr_accessor :measurements
6
+
7
+ def initialize(size)
8
+ @size = size
9
+ @measurements = []
10
+ @current_index = 0
11
+ @sorted_measurements = {}
12
+ end
13
+
14
+ def fill(measurement)
15
+ if reservoir_full?
16
+ update_reservoir_at_current_index(measurement) if should_replace_at_current_index?
17
+ else
18
+ update_reservoir_at_current_index(measurement)
19
+ end
20
+ update_index
21
+ end
22
+
23
+ def calculate_percentile(percentile, metric)
24
+ return 0 unless size_for_calculation > 0
25
+ measurements_for_metric = sorted_measurements_for(metric)
26
+ index = size_for_calculation * percentile
27
+ measurements_for_metric[index].send(metric.to_sym)
28
+ end
29
+
30
+ def calculate_standard_deviation(metric)
31
+ return 0 unless size_for_calculation > 0
32
+ measurements_for_metric = sorted_measurements_for(metric)
33
+ standard_deviation_on(measurements_for_metric, metric)
34
+ end
35
+
36
+ private
37
+
38
+ def standard_deviation_on(list, metric)
39
+ sd = StandardDeviator.new(metric)
40
+ list.each do |measurement|
41
+ sd.calculate measurement
42
+ end
43
+ sd.standard_deviation
44
+ end
45
+
46
+ def sorted_measurements_for(metric)
47
+ @sorted_measurements[metric.to_sym] ||= measurements.sort_by(&metric.to_sym)
48
+ end
49
+
50
+ def update_reservoir_at_current_index(measurement)
51
+ @measurements[@current_index] = measurement
52
+ @sorted_measurements.clear
53
+ end
54
+
55
+ def should_replace_at_current_index?
56
+ #chance = (@current_index.to_f / size_for_calculation.to_f) * 100
57
+ #replace = rand(chance) <= (100.0 / @current_round.to_f)
58
+ true
59
+ end
60
+
61
+ def simulate_chance(chance)
62
+ rand + (1-chance) >= 1
63
+ end
64
+
65
+ def update_index
66
+ if @current_index >= size - 1
67
+ @current_index = 0
68
+ else
69
+ @current_index+= 1
70
+ end
71
+ end
72
+
73
+ def size_for_calculation
74
+ reservoir_full? ? size : measurements.size
75
+ end
76
+
77
+ def reservoir_full?
78
+ size == measurements.size
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,39 @@
1
+ module ActiveMetric
2
+ class StandardDeviator
3
+ attr_reader :count, :sum, :sum_of_squares, :property
4
+
5
+ def initialize(property)
6
+ @count = 0
7
+ @property = property
8
+ @sum = 0
9
+ @sum_of_squares = 0
10
+ @standard_deviation = 0.0
11
+ end
12
+
13
+ def calculate(measurement)
14
+ @standard_deviation = nil
15
+ @count +=1
16
+ value = measurement.send(property)
17
+ @sum += value
18
+ @sum_of_squares += value * value
19
+ end
20
+
21
+ def standard_deviation
22
+ @standard_deviation ||= calculate_standard_deviation
23
+ end
24
+
25
+ def calculate_standard_deviation
26
+ diff = mean_squares - (mean * mean)
27
+ Math.sqrt(diff)
28
+ end
29
+
30
+ def mean
31
+ sum.to_f / count
32
+ end
33
+
34
+ def mean_squares
35
+ sum_of_squares.to_f / count
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,4 @@
1
+ ActiveSupport::Inflector.inflections do |inflect|
2
+ inflect.plural('delta','deltas')
3
+ inflect.singular('delta','delta')
4
+ end
@@ -0,0 +1,60 @@
1
+ module ActiveMetric
2
+
3
+ class GraphViewModel
4
+ include Mongoid::Document
5
+
6
+ belongs_to :subject, :class_name => "ActiveMetric::Subject", :polymorphic => true
7
+ index({:subject_id => 1},{:background => true})
8
+
9
+ field :name
10
+ embeds_many :series_data, :class_name => "ActiveMetric::SeriesData"
11
+ embeds_many :y_axises, class_name: "ActiveMetric::Axis"
12
+
13
+ def ordered_y_axises
14
+ y_axises.asc(:index)
15
+ end
16
+
17
+ def self.create_from_meta_data(axises_defined,stats_defined, options = {})
18
+ graph = self.new(options)
19
+ graph.populate_axises(axises_defined)
20
+ graph.populate_series(stats_defined)
21
+ graph.save!
22
+ graph
23
+ end
24
+
25
+ def populate_axises(axises_defined)
26
+ axises_defined.each do |axis_options|
27
+ delete_axis_at_index(axis_options[:index])
28
+ y_axises << Axis.new(axis_options)
29
+ end
30
+ end
31
+
32
+ def populate_series(stats_defined)
33
+ stats_defined.each do |stat_definition|
34
+ series_data << PointSeriesData.from_stat_definition(stat_definition) if stat_definition.graphable?
35
+ end
36
+ end
37
+
38
+ def series_for(label)
39
+ series_data.select{|series| series.label.eql? label}.first
40
+ end
41
+
42
+ def size
43
+ return 0 unless series_data.size > 0
44
+ series_data.first.size
45
+ end
46
+
47
+ def delete_axis_at_index(index)
48
+ existing_axises = y_axises.select{|axis| axis.index == index}
49
+ if existing_axises.size > 0
50
+ existing_axises.each do |existing_axis|
51
+ y_axises.delete(existing_axis)
52
+ end
53
+ end
54
+ end
55
+
56
+
57
+ end
58
+
59
+
60
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveMetric
2
+
3
+ class Measurement
4
+ include Mongoid::Document
5
+
6
+ #belongs_to :report, :class => "ActiveMetric::Report"
7
+ field :timestamp, :type => Integer, :default => -> {Time.now}
8
+
9
+ def time
10
+ Time.at((timestamp.to_i / 1000).to_i).to_datetime
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveMetric
2
+ class PointSeriesData < SeriesData
3
+
4
+ field :data, :type => Array, :default => []
5
+
6
+ delegate :size, :to => :data
7
+
8
+ def push_data(value)
9
+ push_all(:data, [value])
10
+ end
11
+
12
+ def pop_data(data_to_pop = 1)
13
+ pop(:data,data_to_pop)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,57 @@
1
+ module ActiveMetric
2
+ class Report
3
+ include Mongoid::Document
4
+
5
+ before_save :save_display_name
6
+
7
+ field :display_name
8
+
9
+ has_many :subjects, :class_name => "ActiveMetric::Subject", :dependent => :destroy
10
+
11
+ def name
12
+ "report"
13
+ end
14
+
15
+ def save_display_name
16
+ self.display_name ||= set_display_name
17
+ end
18
+
19
+ def set_display_name
20
+ "Report"
21
+ end
22
+
23
+ def series
24
+ subjects.map(&:series).flatten
25
+ end
26
+
27
+ def min
28
+ 0
29
+ end
30
+
31
+ def view_model
32
+ nil
33
+ end
34
+
35
+ def subjects_by_type
36
+ subjects_type = {}
37
+ subjects.each do |subject|
38
+ type = subject.class.name.split("::").last.underscore
39
+ subjects_type[type] ||= []
40
+ subjects_type[type] << subject
41
+ end
42
+ subjects_type
43
+ end
44
+
45
+ def method_missing(method, *args)
46
+ subject_class = subject_class_for_report(method)
47
+ subject_class ? subject_class.where(report_id: id).all :
48
+ super(method, *args)
49
+ end
50
+
51
+ def subject_class_for_report(method)
52
+ return "#{self.class.parent}::#{method.to_s.classify}".constantize if method.to_s.match /subjects$/
53
+ rescue NameError
54
+ end
55
+
56
+ end
57
+ end