data_store 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,8 +1,11 @@
1
1
  *.gem
2
2
  *.rbc
3
+ *.log
3
4
  .bundle
4
5
  .config
5
6
  .yardoc
7
+ .rbx/
8
+ config/emonweb.yml
6
9
  Gemfile.lock
7
10
  InstalledFiles
8
11
  _yardoc
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@data_store --create
data/.travis.yml ADDED
@@ -0,0 +1,27 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - rbx-19mode
5
+ - jruby-19mode
6
+
7
+ matrix:
8
+ exclude:
9
+ - rvm: jruby-19mode
10
+ env: DB=sqlite
11
+ - rvm: rbx-19mode
12
+ env: DB=sqlite
13
+
14
+ branches:
15
+ only:
16
+ - master
17
+
18
+ script: "bundle exec rake test"
19
+
20
+ env:
21
+ - DB=postgres
22
+ - DB=mysql
23
+ - DB=sqlite
24
+
25
+ before_script:
26
+ - psql -c 'create database data_store_test;' -U postgres
27
+ - mysql -e 'create database data_store_test'
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ ##0.0.2
2
+
3
+ * Readme driven development
4
+ * Configuration of DataStore with Connector class for database connection and dataset definition
5
+ * Introduction of DataStore::Base enriched behaviour with the use of Sequel::Model
6
+ * introduction of the DataStore::Table to add datapoints
7
+
8
+ ##0.0.1
9
+
10
+ * Initial commit and release of gem
data/Gemfile CHANGED
@@ -2,3 +2,33 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in data_store.gemspec
4
4
  gemspec
5
+
6
+ gem 'rake'
7
+ gem 'sequel'
8
+
9
+ platforms :jruby do
10
+ gem 'jdbc-mysql'
11
+ gem 'jdbc-sqlite3'
12
+ gem 'jdbc-postgres', '9.1.901' #9.2 throws an error NameError: missing class or uppercase package name (`org.postgresql.Driver')
13
+ end
14
+
15
+ platforms :ruby do
16
+ gem 'mysql2'
17
+ gem 'sqlite3'
18
+ gem 'pg'
19
+ end
20
+
21
+ gem 'celluloid'
22
+
23
+ group :test, :development do
24
+ gem 'shoulda'
25
+ gem 'mocha'
26
+ gem 'guard'
27
+ gem 'guard-test'
28
+ gem 'rb-fsevent'
29
+ gem 'pry'
30
+ platforms :ruby do
31
+ gem 'yard'
32
+ gem 'redcarpet'
33
+ end
34
+ end
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :test do
5
+ watch(%r{^lib/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
6
+ watch(%r{^lib/data_store/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
7
+ watch(%r{^test/.+_test\.rb$})
8
+ watch('test/test_helper.rb') { "test" }
9
+ watch(%r{^lib/data_store/base.rb$}) { "test" }
10
+ end
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Frank Oxener
1
+ Copyright (c) 2012 Agile Dovadi B.V. - Frank Oxener
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # DataStore
2
2
 
3
- TODO: Write a gem description
3
+ <a href='http://travis-ci.org/dovadi/data_store'>
4
+ ![http://travis-ci.org/dovadi/data_store](https://secure.travis-ci.org/dovadi/data_store.png)
5
+ </a>
6
+
7
+ DataStore is designed to store real time data and manage the growth of your dataset by deciding the time period of your historical data. DataStore is tested with Ruby 1.9.3, Rubinius and JRuby and works with three database adapters Sqlite, Mysql and Postgresql.
4
8
 
5
9
  ## Installation
6
10
 
@@ -18,7 +22,86 @@ Or install it yourself as:
18
22
 
19
23
  ## Usage
20
24
 
21
- TODO: Write usage instructions here
25
+ ### Configuration
26
+
27
+ DataStore.configure do |config|
28
+ config.prefix = 'ds_'
29
+ config.database = :mysql
30
+ config.compression_schema = [6,5,3]
31
+ config.data_type = :double
32
+ config.frequency = 10
33
+ config.maximum_datapoints = 800
34
+ config.log_file = 'data_store.log'
35
+ config.log_level = Logger::INFO
36
+ end
37
+
38
+ ### Creation of a DataStore
39
+
40
+ DataStore::Base.create(identifier: 1, type: 'gauge', name: 'Electra', description: 'Actual usage of electra in the home')
41
+
42
+ This will result in the creation of 4 tables,
43
+
44
+ 'ds_1'
45
+ 'ds_1_6'
46
+ 'ds_1_30'
47
+ 'ds_1_90'
48
+
49
+ with the following structure:
50
+
51
+ id: integer
52
+ value: double
53
+ created: double #for unix timestamp
54
+
55
+ and a record to the main data_stores table with the corresponding field names
56
+
57
+ id
58
+ name
59
+ description
60
+ compression_schema
61
+ frequency
62
+ maximum_datapoints
63
+ data_type
64
+
65
+ ### Add a datapoint
66
+
67
+ table = DataStore::Table.new(1)
68
+ table.add(120.34)
69
+ table.add(123.09)
70
+ table.add(125.01)
71
+
72
+ ### Fetching datapoints
73
+
74
+ DataStore::Table.new(1).fetch(:from => (Time.now.utc - 3600).to_f, :till => Time.now.utc.to_f)
75
+
76
+ will result in an array of the maximum data points. An data point consists of an unix timestamp (UTC) and a value
77
+
78
+ [[1352668356, 120], [1352678356, 123.09], [1352688356, 125.01]]
79
+
80
+ ### Getting meta data of your data set
81
+
82
+ DataStore::Table.new(1).parent
83
+
84
+ will return the corresponding record from the general data_stores table
85
+
86
+ or more specific count of the number of records
87
+
88
+ DataStore::Table.new(1).count #=> 1249336
89
+
90
+ last record
91
+
92
+ DataStore::Table.new(1).last
93
+
94
+ results
95
+
96
+ #< @values={:id=>2, :value=>120.38, :created=>1356621436.67489}>
97
+
98
+ ### Managing the size of your data set
99
+
100
+ ### Export a data store (NOT implemented yet)
101
+
102
+ DDataStore::Table.new(1).export
103
+
104
+ will result in a csv file with the name data_store_1.csv
22
105
 
23
106
  ## Contributing
24
107
 
@@ -27,3 +110,9 @@ TODO: Write usage instructions here
27
110
  3. Commit your changes (`git commit -am 'Added some feature'`)
28
111
  4. Push to the branch (`git push origin my-new-feature`)
29
112
  5. Create new Pull Request
113
+
114
+ ## Copyright
115
+
116
+ Copyright (c) 2013 Agile Dovadi BV - Frank Oxener.
117
+
118
+ See LICENSE.txt for further details.
data/REMINDERS ADDED
@@ -0,0 +1,8 @@
1
+ Publish gem
2
+ ===========
3
+
4
+ # git push
5
+ # git tag -a 0.0.1 -m "Initial commit"
6
+ # git push --tags
7
+ # gem build data_store.gemspec
8
+ # gem push data_store-0.0.1.gem
data/Rakefile CHANGED
@@ -1,2 +1,14 @@
1
1
  #!/usr/bin/env rake
2
- require 'bundler/gem_tasks'
2
+ # encoding: utf-8
3
+ require 'rubygems'
4
+ require 'bundler/gem_helper'
5
+ Bundler::GemHelper.install_tasks
6
+
7
+ require 'rake/testtask'
8
+ Rake::TestTask.new(:test) do |test|
9
+ test.libs << 'lib' << 'test'
10
+ test.pattern = 'test/**/*_test.rb'
11
+ test.verbose = true
12
+ end
13
+
14
+ task :default => :test
@@ -0,0 +1,16 @@
1
+ postgres:
2
+ adapter: postgres
3
+ username: postgres
4
+ password:
5
+ database: data_store_test
6
+ host: localhost
7
+
8
+ sqlite:
9
+ adapter: sqlite
10
+ database: db/data_store.db
11
+
12
+ mysql:
13
+ adapter: mysql2
14
+ username: root
15
+ database: data_store_test
16
+ host: localhost
data/data_store.gemspec CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
6
6
  gem.email = ['frank.oxener@gmail.com']
7
7
  gem.description = %q{DataStore is designed to store real time data but still manage the growth of your dataset and still keeping historical data}
8
8
  gem.summary = %q{DataStore for storing real time data}
9
- gem.homepage = ''
9
+ gem.homepage = 'https://github.com/dovadi/data_store'
10
10
 
11
11
  gem.licenses = ['MIT']
12
12
  gem.files = `git ls-files`.split($\)
@@ -15,7 +15,4 @@ Gem::Specification.new do |gem|
15
15
  gem.name = 'data_store'
16
16
  gem.require_paths = ['lib']
17
17
  gem.version = DataStore::VERSION
18
-
19
- gem.add_development_dependency 'shoulda'
20
- gem.add_development_dependency 'rspec'
21
18
  end
data/db/data_store.db ADDED
Binary file
@@ -0,0 +1,93 @@
1
+ module DataStore
2
+
3
+ class AverageCalculator
4
+
5
+ TIMESTAMP_CORRECTION_FACTOR = 0.0001
6
+
7
+ attr_reader :identifier, :base, :table_index, :table
8
+
9
+ def initialize(table)
10
+ @table = table
11
+ @identifier = table.identifier
12
+ @table_index = table.table_index
13
+ @base = Base.find(identifier: identifier)
14
+ end
15
+
16
+ # Calculate average value if needed
17
+ # Average value is store dthrough an add call by a Table object
18
+ # So the average calculator is called again recursively
19
+ def perform
20
+ if calculation_needed?
21
+ average = previous_average_record ? calculate! : dataset.avg(:value)
22
+ table.add(average, table_index: table_index + 1, created: last[:created], type: :gauge)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def calculate!
29
+ last_time = previous_average_record[:created] + TIMESTAMP_CORRECTION_FACTOR
30
+ dataset.where{created > last_time}.avg(:value)
31
+ end
32
+
33
+ def calculation_needed?
34
+ return false if compression_finished
35
+ if previous_average_record
36
+ tolerance = DataStore.configuration.frequency_tolerance
37
+ time_difference_since_last_calculation >= (time_resolution - (time_resolution * tolerance))
38
+ else
39
+ dataset.count == base.compression_schema[table_index]
40
+ end
41
+ end
42
+
43
+ def time_difference_since_last_calculation
44
+ last[:created].round - previous_average_record[:created].round
45
+ end
46
+
47
+ def time_resolution
48
+ table.parent.frequency * compression_factors[table_index]
49
+ end
50
+
51
+ def compression_factors
52
+ array, factor = [], 1
53
+ base.compression_schema.each do |compression|
54
+ factor = (factor * compression)
55
+ array << factor
56
+ end
57
+ array
58
+ end
59
+
60
+ def previous_average_record
61
+ base.db[next_table].order(:created).last
62
+ end
63
+
64
+ def compression_finished
65
+ table_index == base.compression_schema.size
66
+ end
67
+
68
+ def last
69
+ dataset.order(:created).last
70
+ end
71
+
72
+ def dataset
73
+ base.db[table_name]
74
+ end
75
+
76
+ def table_name
77
+ if table_index == 0
78
+ prefix.chop.to_sym
79
+ else
80
+ (prefix + compression_factors[table_index - 1].to_s).to_sym
81
+ end
82
+ end
83
+
84
+ def next_table
85
+ (prefix + compression_factors[table_index].to_s).to_sym
86
+ end
87
+
88
+ def prefix
89
+ DataStore.configuration.prefix + identifier.to_s + '_'
90
+ end
91
+ end
92
+
93
+ end
@@ -0,0 +1,108 @@
1
+ module DataStore
2
+
3
+ class Base
4
+
5
+ # Set the default values with the globally defined values
6
+ # * :compression_schema
7
+ # * :frequency
8
+ # * :maximum_datapoints
9
+ # * :data_type
10
+ # See {Configuration}
11
+ def before_save
12
+ set_default_values
13
+ end
14
+
15
+ def after_create
16
+ drop_tables!
17
+ create_tables!
18
+ end
19
+
20
+ def before_destroy
21
+ drop_tables!
22
+ end
23
+
24
+ # Convert serialized compression schema as a string back into the array object itself.
25
+ # For example: "[5,4,3]" => [5,4,3]
26
+ def compression_schema
27
+ value = self.values[:compression_schema]
28
+ value.gsub(/\[|\]/,'').split(',').map(&:to_i) unless value.nil?
29
+ end
30
+
31
+ def table_names
32
+ names = [table_name]
33
+ factor = 1
34
+ compression_schema.each do |compression|
35
+ factor = (factor * compression)
36
+ names << (table_name.to_s + '_' + factor.to_s).to_sym
37
+ end
38
+ names
39
+ end
40
+
41
+ def time_borders
42
+ width = time_width
43
+ borders = [width]
44
+ compression_schema.each do |compression|
45
+ width = width * compression
46
+ borders << width
47
+ end
48
+ borders
49
+ end
50
+
51
+ def time_width
52
+ DataStore.configuration.frequency * DataStore.configuration.maximum_datapoints
53
+ end
54
+
55
+ private
56
+
57
+ def default_values
58
+ ['compression_schema', 'frequency', 'maximum_datapoints', 'data_type']
59
+ end
60
+
61
+ def set_default_values
62
+ default_values.each do |variable|
63
+ value = DataStore.configuration.send(variable)
64
+ self.send(variable+ '=', value) if self.send(variable).nil?
65
+ end
66
+ end
67
+
68
+ # Create the database tables which are used for storing the datapoints
69
+ def create_tables!
70
+ migrate(:up)
71
+ end
72
+
73
+ # Drop the database tables which are used for storing the datapoints
74
+ def drop_tables!
75
+ migrate(:down)
76
+ end
77
+
78
+ def migrate(direction = :up)
79
+ # Establish new connection to prevent mix up with associated db connection of the Base object
80
+ # Unless connected to a sqlite db, otherwise it is too time consuming
81
+ database = sqlite_db? ? db : DataStore::Connector.new.database
82
+ table_names.each do |name|
83
+ begin
84
+ settings = {name: name, data_type: data_type}
85
+ settings[:original_value] = type == 'counter'
86
+ DataStore.create_table(settings).apply(database, direction)
87
+ rescue Sequel::DatabaseError => e
88
+ raise e if e.message.include?('FATAL')
89
+ end
90
+ end
91
+ database.disconnect unless sqlite_db?
92
+ end
93
+
94
+ def table_name
95
+ (prefix + identifier.to_s).to_sym
96
+ end
97
+
98
+ def prefix
99
+ DataStore.configuration.prefix
100
+ end
101
+
102
+ def sqlite_db?
103
+ DataStore.configuration.database.to_s == 'sqlite'
104
+ end
105
+
106
+ end
107
+
108
+ end
@@ -0,0 +1,66 @@
1
+ module DataStore
2
+
3
+ # Used to set up and modify settings for the data store.
4
+ class Configuration
5
+
6
+ # The prefix is used as a prefix for the database table name.
7
+ attr_accessor :prefix
8
+
9
+ # The database used for storing the data.
10
+ attr_accessor :database
11
+
12
+ # The schema is the way avarages of the datapoints is calculated/
13
+ # Default: [6,5,3,4,4,3]
14
+ attr_accessor :compression_schema
15
+
16
+ # The frequency tells how often data entry is done.
17
+ # A frequency of 10 means a data entry once every 10 seconds.
18
+ # Default: 10 sec
19
+ attr_accessor :frequency
20
+
21
+ # Tolerance of the frequency in which datapoints are added
22
+ # Default: 0.05
23
+ # This means a 5% margin. So with a frequency of 10s,
24
+ # the next datapoint within 9.95 - 10.5 is considered the next datapoint
25
+ attr_accessor :frequency_tolerance
26
+
27
+ # The maximum datapoints is the maximum number of datapoint within a given timeframe
28
+ # Default: 800
29
+ attr_accessor :maximum_datapoints
30
+
31
+ # The data type in which the value is stored
32
+ # Default: double
33
+ attr_accessor :data_type
34
+
35
+ #The location of the database.yml file
36
+ attr_accessor :database_config_file
37
+
38
+ #Enable logging.
39
+ # Default: true
40
+ attr_accessor :enable_logging
41
+
42
+ #The location of the log file
43
+ # Default $stdout
44
+ attr_accessor :log_file
45
+
46
+ #The level of logging
47
+ # Default: Logger::ERROR
48
+ attr_accessor :log_level
49
+
50
+ def initialize
51
+ @prefix = 'ds_'
52
+ @database = :postgres
53
+ @compression_schema = [6,5,3,4,4,3]
54
+ @frequency = 10
55
+ @maximum_datapoints = 800
56
+ @data_type = :double
57
+ @database_config_file = File.expand_path('../../../config/database.yml', __FILE__)
58
+ @log_file = $stdout
59
+ @log_level = Logger::ERROR
60
+ @enable_logging = true
61
+ @frequency_tolerance = 0.05
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,85 @@
1
+ module DataStore
2
+
3
+ class Connector
4
+
5
+ # Create the data_stores table with the following attributes
6
+ #
7
+ # * primary_key :id
8
+ # * Integer :identifier, unique: true, null: false
9
+ # * String :name, null: false
10
+ # * String :type, null: false
11
+ # * String :description
12
+ # * DateTime :created_at
13
+ # * DateTime :updated_at
14
+ #
15
+ def create_table!
16
+ DataStore.create_data_stores.apply(database, :up)
17
+ rescue Sequel::DatabaseError => e
18
+ raise e if e.message.include?('FATAL')
19
+ end
20
+
21
+ # Drop data_stores table and recreate it
22
+ def reset!
23
+ drop_table!
24
+ create_table!
25
+ disconnect
26
+ end
27
+
28
+ # Return the dataset associated with data_stores
29
+ def dataset
30
+ @dataset ||= begin
31
+ create_table!
32
+ database[:data_stores]
33
+ end
34
+ end
35
+
36
+ # Return the database object to which its connected.
37
+ def database
38
+ @database ||= begin
39
+ if RUBY_PLATFORM == 'java'
40
+ Sequel.connect(jdbc_settings)
41
+ else
42
+ Sequel.connect(database_settings)
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def jdbc_settings
50
+ settings = database_settings
51
+ db = case settings['adapter']
52
+ when 'postgres'
53
+ 'postgresql'
54
+ when 'mysql2'
55
+ 'mysql'
56
+ else
57
+ settings['adapter']
58
+ end
59
+ if db == 'sqlite'
60
+ uri = "jdbc:#{db}:#{settings['database']}"
61
+ else
62
+ uri = "jdbc:#{db}://#{settings['host']}/#{settings['database']}?user=#{settings['username']}"
63
+ end
64
+ settings['password'] ? uri + "&password=#{settings['password']}" : uri
65
+ end
66
+
67
+ def disconnect
68
+ database.disconnect
69
+ @database = nil
70
+ end
71
+
72
+ def drop_table!
73
+ DataStore.create_data_stores.apply(database, :down)
74
+ rescue Sequel::DatabaseError => e
75
+ raise e if e.message.include?('FATAL')
76
+ end
77
+
78
+ def database_settings
79
+ config_file = DataStore.configuration.database_config_file
80
+ YAML.load(File.open(config_file))[DataStore.configuration.database.to_s]
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,42 @@
1
+ module DataStore
2
+
3
+ # Definition of the data_stores table
4
+ def self.create_data_stores
5
+ Sequel.migration do
6
+ change do
7
+ create_table(:data_stores) do
8
+ primary_key :id
9
+ Integer :identifier, unique: true, null: false
10
+ String :name, null: false
11
+ String :type, null: false
12
+ String :description
13
+ String :data_type
14
+ String :compression_schema
15
+ Integer :frequency
16
+ Integer :maximum_datapoints
17
+ DateTime :created_at
18
+ DateTime :updated_at
19
+ index :identifier
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def self.create_table(settings)
26
+ name = settings[:name]
27
+ original_value = settings[:original_value] || false
28
+ data_type = settings[:data_type]
29
+
30
+ Sequel.migration do
31
+ change do
32
+ create_table(name) do
33
+ primary_key :id
34
+ column :value, data_type
35
+ column :original_value, data_type if original_value
36
+ column :created, :double
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ end