amalgalite 0.10.1-x86-mingw32
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/HISTORY +201 -0
- data/LICENSE +29 -0
- data/README +51 -0
- data/bin/amalgalite-pack +126 -0
- data/examples/a.rb +9 -0
- data/examples/blob.rb +88 -0
- data/examples/bootstrap.rb +36 -0
- data/examples/define_aggregate.rb +75 -0
- data/examples/define_function.rb +104 -0
- data/examples/gem-db.rb +94 -0
- data/examples/gems.db +0 -0
- data/examples/require_me.rb +11 -0
- data/examples/requires.rb +42 -0
- data/examples/schema-info.rb +34 -0
- data/ext/amalgalite/amalgalite3.c +290 -0
- data/ext/amalgalite/amalgalite3.h +151 -0
- data/ext/amalgalite/amalgalite3_blob.c +240 -0
- data/ext/amalgalite/amalgalite3_constants.c +221 -0
- data/ext/amalgalite/amalgalite3_database.c +1148 -0
- data/ext/amalgalite/amalgalite3_requires_bootstrap.c +210 -0
- data/ext/amalgalite/amalgalite3_statement.c +639 -0
- data/ext/amalgalite/extconf.rb +36 -0
- data/ext/amalgalite/gen_constants.rb +130 -0
- data/ext/amalgalite/sqlite3.c +106729 -0
- data/ext/amalgalite/sqlite3.h +5626 -0
- data/ext/amalgalite/sqlite3_options.h +4 -0
- data/ext/amalgalite/sqlite3ext.h +380 -0
- data/gemspec.rb +60 -0
- data/lib/amalgalite.rb +43 -0
- data/lib/amalgalite/1.8/amalgalite3.so +0 -0
- data/lib/amalgalite/1.9/amalgalite3.so +0 -0
- data/lib/amalgalite/aggregate.rb +67 -0
- data/lib/amalgalite/blob.rb +186 -0
- data/lib/amalgalite/boolean.rb +42 -0
- data/lib/amalgalite/busy_timeout.rb +47 -0
- data/lib/amalgalite/column.rb +97 -0
- data/lib/amalgalite/core_ext/kernel/require.rb +21 -0
- data/lib/amalgalite/database.rb +947 -0
- data/lib/amalgalite/function.rb +61 -0
- data/lib/amalgalite/index.rb +43 -0
- data/lib/amalgalite/packer.rb +226 -0
- data/lib/amalgalite/paths.rb +70 -0
- data/lib/amalgalite/profile_tap.rb +131 -0
- data/lib/amalgalite/progress_handler.rb +21 -0
- data/lib/amalgalite/requires.rb +120 -0
- data/lib/amalgalite/schema.rb +191 -0
- data/lib/amalgalite/sqlite3.rb +6 -0
- data/lib/amalgalite/sqlite3/constants.rb +80 -0
- data/lib/amalgalite/sqlite3/database/function.rb +48 -0
- data/lib/amalgalite/sqlite3/database/status.rb +68 -0
- data/lib/amalgalite/sqlite3/status.rb +60 -0
- data/lib/amalgalite/sqlite3/version.rb +37 -0
- data/lib/amalgalite/statement.rb +414 -0
- data/lib/amalgalite/table.rb +90 -0
- data/lib/amalgalite/taps.rb +2 -0
- data/lib/amalgalite/taps/console.rb +27 -0
- data/lib/amalgalite/taps/io.rb +71 -0
- data/lib/amalgalite/trace_tap.rb +35 -0
- data/lib/amalgalite/type_map.rb +63 -0
- data/lib/amalgalite/type_maps/default_map.rb +167 -0
- data/lib/amalgalite/type_maps/storage_map.rb +40 -0
- data/lib/amalgalite/type_maps/text_map.rb +22 -0
- data/lib/amalgalite/version.rb +37 -0
- data/lib/amalgalite/view.rb +26 -0
- data/spec/aggregate_spec.rb +169 -0
- data/spec/amalgalite_spec.rb +4 -0
- data/spec/blob_spec.rb +81 -0
- data/spec/boolean_spec.rb +23 -0
- data/spec/busy_handler.rb +165 -0
- data/spec/database_spec.rb +494 -0
- data/spec/default_map_spec.rb +87 -0
- data/spec/function_spec.rb +94 -0
- data/spec/integeration_spec.rb +111 -0
- data/spec/packer_spec.rb +60 -0
- data/spec/paths_spec.rb +28 -0
- data/spec/progress_handler_spec.rb +105 -0
- data/spec/requires_spec.rb +23 -0
- data/spec/rtree_spec.rb +71 -0
- data/spec/schema_spec.rb +120 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/sqlite3/constants_spec.rb +65 -0
- data/spec/sqlite3/database_status_spec.rb +36 -0
- data/spec/sqlite3/status_spec.rb +18 -0
- data/spec/sqlite3/version_spec.rb +14 -0
- data/spec/sqlite3_spec.rb +53 -0
- data/spec/statement_spec.rb +161 -0
- data/spec/storage_map_spec.rb +41 -0
- data/spec/tap_spec.rb +59 -0
- data/spec/text_map_spec.rb +23 -0
- data/spec/type_map_spec.rb +17 -0
- data/spec/version_spec.rb +15 -0
- data/tasks/announce.rake +43 -0
- data/tasks/config.rb +107 -0
- data/tasks/distribution.rake +77 -0
- data/tasks/documentation.rake +32 -0
- data/tasks/extension.rake +141 -0
- data/tasks/rspec.rake +33 -0
- data/tasks/rubyforge.rake +59 -0
- data/tasks/utils.rb +80 -0
- metadata +237 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'amalgalite/sqlite3/database/function'
|
2
|
+
module Amalgalite
|
3
|
+
#
|
4
|
+
# A Base class to inherit from for creating your own SQL scalar functions
|
5
|
+
# in ruby.
|
6
|
+
#
|
7
|
+
# These are SQL functions similar to _abs(X)_, _length(X)_, _random()_. Items
|
8
|
+
# that take parameters and return value. They have no state between
|
9
|
+
# calls. Built in SQLite scalar functions are :
|
10
|
+
#
|
11
|
+
# * http://www.sqlite.org/lang_corefunc.html
|
12
|
+
# * http://www.sqlite.org/lang_datefunc.html
|
13
|
+
#
|
14
|
+
# Functions defined in Amalgalite databases conform to the Proc interface.
|
15
|
+
# Everything that is defined in an Amalgalite database using +define_function+
|
16
|
+
# has its +to_proc+ method called. As a result, any Function must also
|
17
|
+
# conform to the +to_proc+ protocol.
|
18
|
+
#
|
19
|
+
# If you choose to use Function as a parent class of your SQL scalar function
|
20
|
+
# implementation you should only have implement +call+ with the appropriate
|
21
|
+
# _arity_.
|
22
|
+
#
|
23
|
+
# For instance to implement a _sha1(X)_ SQL function you could implement it as
|
24
|
+
#
|
25
|
+
# class SQLSha1 < ::Amalgalite::Function
|
26
|
+
# def initialize
|
27
|
+
# super( 'md5', 1 )
|
28
|
+
# end
|
29
|
+
# def call( s )
|
30
|
+
# ::Digest::MD5.hexdigest( s.to_s )
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
class Function
|
35
|
+
# The name of the SQL function
|
36
|
+
attr_accessor :name
|
37
|
+
|
38
|
+
# The arity of the SQL function
|
39
|
+
attr_accessor :arity
|
40
|
+
|
41
|
+
# Initialize the function with a name and arity
|
42
|
+
def initialize( name, arity )
|
43
|
+
@name = name
|
44
|
+
@arity = arity
|
45
|
+
end
|
46
|
+
|
47
|
+
# All SQL functions defined foloow the +to_proc+ protocol
|
48
|
+
def to_proc
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# <b>Do Not Override</b>
|
53
|
+
#
|
54
|
+
# The function signature for use by the Amaglaite datase in tracking
|
55
|
+
# function definition and removal.
|
56
|
+
#
|
57
|
+
def signature
|
58
|
+
@signature ||= ::Amalgalite::SQLite3::Database::Function.signature( self.name, self.arity )
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module Amalgalite
|
7
|
+
#
|
8
|
+
# a class representing the meta information about an SQLite index
|
9
|
+
#
|
10
|
+
class Index
|
11
|
+
# the name of the index
|
12
|
+
attr_reader :name
|
13
|
+
|
14
|
+
# the sql statement that created the index
|
15
|
+
attr_reader :sql
|
16
|
+
|
17
|
+
# the table the index is for
|
18
|
+
attr_accessor :table
|
19
|
+
|
20
|
+
# the columns that make up this index, in index order
|
21
|
+
attr_accessor :columns
|
22
|
+
|
23
|
+
# sqlite sequence number of the index
|
24
|
+
attr_accessor :sequence_number
|
25
|
+
|
26
|
+
# is the index unique
|
27
|
+
attr_writer :unique
|
28
|
+
|
29
|
+
def initialize( name, sql, table )
|
30
|
+
@name = name
|
31
|
+
@sql = sql
|
32
|
+
@table = table
|
33
|
+
@columns = []
|
34
|
+
@sequence_number = nil
|
35
|
+
@unique = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def unique?
|
39
|
+
return @unique
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'pathname'
|
4
|
+
require 'zlib'
|
5
|
+
|
6
|
+
require 'amalgalite'
|
7
|
+
module Amalgalite
|
8
|
+
#
|
9
|
+
# Pack items into an amalgalite database.
|
10
|
+
#
|
11
|
+
class Packer
|
12
|
+
attr_reader :packing_list
|
13
|
+
attr_reader :dbfile
|
14
|
+
attr_reader :options
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def default_options
|
18
|
+
{
|
19
|
+
:table_name => Requires::Bootstrap::DEFAULT_TABLE,
|
20
|
+
:filename_column => Requires::Bootstrap::DEFAULT_FILENAME_COLUMN,
|
21
|
+
:contents_column => Requires::Bootstrap::DEFAULT_CONTENTS_COLUMN,
|
22
|
+
:compressed_column => Requires::Bootstrap::DEFAULT_COMPRESSED_COLUMN,
|
23
|
+
:strip_prefix => Dir.pwd,
|
24
|
+
:compressed => false,
|
25
|
+
:verbose => false,
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# compress data
|
31
|
+
#
|
32
|
+
def gzip( data )
|
33
|
+
zipped = StringIO.new
|
34
|
+
Zlib::GzipWriter.wrap( zipped ) do |io|
|
35
|
+
io.write( data )
|
36
|
+
end
|
37
|
+
return zipped.string
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# uncompress gzip data
|
42
|
+
#
|
43
|
+
def gunzip( data )
|
44
|
+
data = StringIO.new( data )
|
45
|
+
Zlib::GzipReader.new( data ).read
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
#
|
50
|
+
# return the files in their dependency order for use for packing into a
|
51
|
+
# database
|
52
|
+
#
|
53
|
+
def amalgalite_require_order
|
54
|
+
@require_order ||= %w[
|
55
|
+
amalgalite.rb
|
56
|
+
amalgalite/sqlite3/database/function.rb
|
57
|
+
amalgalite/aggregate.rb
|
58
|
+
amalgalite/blob.rb
|
59
|
+
amalgalite/boolean.rb
|
60
|
+
amalgalite/busy_timeout.rb
|
61
|
+
amalgalite/column.rb
|
62
|
+
amalgalite/statement.rb
|
63
|
+
amalgalite/trace_tap.rb
|
64
|
+
amalgalite/profile_tap.rb
|
65
|
+
amalgalite/type_map.rb
|
66
|
+
amalgalite/type_maps/storage_map.rb
|
67
|
+
amalgalite/type_maps/text_map.rb
|
68
|
+
amalgalite/type_maps/default_map.rb
|
69
|
+
amalgalite/function.rb
|
70
|
+
amalgalite/progress_handler.rb
|
71
|
+
amalgalite/database.rb
|
72
|
+
amalgalite/index.rb
|
73
|
+
amalgalite/paths.rb
|
74
|
+
amalgalite/table.rb
|
75
|
+
amalgalite/view.rb
|
76
|
+
amalgalite/schema.rb
|
77
|
+
amalgalite/version.rb
|
78
|
+
amalgalite/sqlite3/version.rb
|
79
|
+
amalgalite/sqlite3/constants.rb
|
80
|
+
amalgalite/sqlite3/status.rb
|
81
|
+
amalgalite/sqlite3/database/status.rb
|
82
|
+
amalgalite/sqlite3.rb
|
83
|
+
amalgalite/taps/io.rb
|
84
|
+
amalgalite/taps/console.rb
|
85
|
+
amalgalite/taps.rb
|
86
|
+
amalgalite/packer.rb
|
87
|
+
amalgalite/core_ext/kernel/require.rb
|
88
|
+
amalgalite/requires.rb
|
89
|
+
]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# Create a new packer instance with the list of items to pack and all the
|
95
|
+
# options
|
96
|
+
#
|
97
|
+
def initialize( options = {} )
|
98
|
+
@options = Packer.default_options.merge( options )
|
99
|
+
@dbfile = @options[:dbfile] || Requires::Bootstrap::DEFAULT_DB
|
100
|
+
end
|
101
|
+
|
102
|
+
#
|
103
|
+
# The SQL to create the table for storing ruby code
|
104
|
+
#
|
105
|
+
def create_table_sql
|
106
|
+
sql = <<-create
|
107
|
+
CREATE TABLE #{options[:table_name]} (
|
108
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
109
|
+
#{options[:filename_column]} TEXT UNIQUE,
|
110
|
+
#{options[:compressed_column]} BOOLEAN,
|
111
|
+
#{options[:contents_column]} BLOB
|
112
|
+
);
|
113
|
+
create
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# Make sure that the dbfile exists and has the appropriate schema.
|
118
|
+
#
|
119
|
+
def check_db( db )
|
120
|
+
if db.schema.tables[ options[:table_name] ] and options[:drop_table] then
|
121
|
+
STDERR.puts "Dropping table #{options[:table_name]}" if options[:verbose]
|
122
|
+
db.execute("DROP TABLE #{options[:table_name]}")
|
123
|
+
db.reload_schema!
|
124
|
+
end
|
125
|
+
|
126
|
+
unless db.schema.tables[ options[:table_name] ]
|
127
|
+
db.execute( create_table_sql )
|
128
|
+
db.reload_schema!
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
#
|
135
|
+
# Stores all the .rb files in the list into the given database. The prefix
|
136
|
+
# is the file system path to remove from the front of the path on each file
|
137
|
+
#
|
138
|
+
# manifest is an array of OpenStructs.
|
139
|
+
#
|
140
|
+
def pack_files( manifest )
|
141
|
+
db = Amalgalite::Database.new( dbfile )
|
142
|
+
check_db( db )
|
143
|
+
max_width = manifest.collect{ |m| m.require_path.length }.sort.last
|
144
|
+
contents_column = db.schema.tables[ options[:table_name] ].columns[ options[:contents_column] ]
|
145
|
+
db.transaction do |trans|
|
146
|
+
manifest.each do |file_info|
|
147
|
+
msg = " -> #{file_info.require_path.ljust( max_width )} : "
|
148
|
+
begin
|
149
|
+
if options[:merge] then
|
150
|
+
trans.execute( "DELETE FROM #{options[:table_name]} WHERE #{options[:filename_column]} = ?", file_info.require_path )
|
151
|
+
end
|
152
|
+
|
153
|
+
trans.prepare("INSERT INTO #{options[:table_name]}(#{options[:filename_column]}, #{options[:compressed_column]}, #{options[:contents_column]}) VALUES( $filename, $compressed, $contents)") do |stmt|
|
154
|
+
contents = IO.readlines( file_info.file_path )
|
155
|
+
if options[:self] then
|
156
|
+
contents.each { |l| l.gsub!( /^(\s*require .*)$/m, "# commented out by #{self.class.name} \\1") }
|
157
|
+
end
|
158
|
+
contents = contents.join
|
159
|
+
|
160
|
+
if options[:compressed] then
|
161
|
+
contents = Packer.gzip( contents )
|
162
|
+
end
|
163
|
+
content_io = StringIO.new( contents )
|
164
|
+
stmt.execute( "$filename" => file_info.require_path,
|
165
|
+
"$contents" => Amalgalite::Blob.new( :io => content_io,
|
166
|
+
:column => contents_column ),
|
167
|
+
"$compressed" => options[:compressed] )
|
168
|
+
STDERR.puts "#{msg} stored #{file_info.file_path}" if options[:verbose]
|
169
|
+
end
|
170
|
+
rescue => e
|
171
|
+
STDERR.puts "#{msg} error #{e}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
#
|
178
|
+
# given a file, see if it can be found in the ruby load path, if so, return that
|
179
|
+
# full path
|
180
|
+
#
|
181
|
+
def full_path_of( rb_file )
|
182
|
+
$LOAD_PATH.each do |load_path|
|
183
|
+
guess = File.expand_path( File.join( load_path, rb_file ) )
|
184
|
+
return guess if File.exist?( guess )
|
185
|
+
end
|
186
|
+
return nil
|
187
|
+
end
|
188
|
+
|
189
|
+
#
|
190
|
+
# Make the manifest for packing
|
191
|
+
#
|
192
|
+
def make_manifest( file_list )
|
193
|
+
manifest = []
|
194
|
+
prefix_path = ::Pathname.new( options[:strip_prefix] )
|
195
|
+
file_list.each do |f|
|
196
|
+
file_path = ::Pathname.new( File.expand_path( f ) )
|
197
|
+
m = ::OpenStruct.new
|
198
|
+
# if it is a directory then grab all the .rb files from it
|
199
|
+
if File.directory?( file_path ) then
|
200
|
+
manifest.concat( make_manifest( Dir.glob( File.join( f, "**", "*.rb" ) ) ) )
|
201
|
+
next
|
202
|
+
elsif File.readable?( file_path ) then
|
203
|
+
m.require_path = file_path.relative_path_from( prefix_path )
|
204
|
+
m.file_path = file_path.realpath.to_s
|
205
|
+
elsif lp = full_path_of( f ) then
|
206
|
+
m.require_path = f
|
207
|
+
m.file_path = lp
|
208
|
+
else
|
209
|
+
STDERR.puts "Unable to add #{f} to the manifest, cannot find the file on disk"
|
210
|
+
next
|
211
|
+
end
|
212
|
+
m.require_path = m.require_path.to_s[ /\A(.*)\.rb\Z/, 1]
|
213
|
+
manifest << m
|
214
|
+
end
|
215
|
+
return manifest
|
216
|
+
end
|
217
|
+
|
218
|
+
#
|
219
|
+
# Given a list of files pack them into the associated database and table.
|
220
|
+
#
|
221
|
+
def pack( file_list )
|
222
|
+
manifest = make_manifest( file_list )
|
223
|
+
pack_files( manifest )
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
module Amalgalite
|
6
|
+
#
|
7
|
+
# Paths contains helpful methods to determine paths of files inside the
|
8
|
+
# Amalgalite library
|
9
|
+
#
|
10
|
+
module Paths
|
11
|
+
#
|
12
|
+
# The root directory of the project is considered to be the parent directory
|
13
|
+
# of the 'lib' directory.
|
14
|
+
#
|
15
|
+
# returns:: [String] The full expanded path of the parent directory of 'lib'
|
16
|
+
# going up the path from the current file. Trailing
|
17
|
+
# File::SEPARATOR is guaranteed.
|
18
|
+
#
|
19
|
+
def self.root_dir
|
20
|
+
@root_dir ||= (
|
21
|
+
path_parts = ::File.expand_path(__FILE__).split(::File::SEPARATOR)
|
22
|
+
lib_index = path_parts.rindex("lib")
|
23
|
+
path_parts[0...lib_index].join(::File::SEPARATOR) + ::File::SEPARATOR
|
24
|
+
)
|
25
|
+
return @root_dir
|
26
|
+
end
|
27
|
+
|
28
|
+
# returns:: [String] The full expanded path of the +config+ directory
|
29
|
+
# below _root_dir_. All parameters passed in are joined onto the
|
30
|
+
# result. Trailing File::SEPARATOR is guaranteed if _args_ are
|
31
|
+
# *not* present.
|
32
|
+
#
|
33
|
+
def self.config_path(*args)
|
34
|
+
self.sub_path("config", *args)
|
35
|
+
end
|
36
|
+
|
37
|
+
# returns:: [String] The full expanded path of the +data+ directory below
|
38
|
+
# _root_dir_. All parameters passed in are joined onto the
|
39
|
+
# result. Trailing File::SEPARATOR is guaranteed if
|
40
|
+
# _*args_ are *not* present.
|
41
|
+
#
|
42
|
+
def self.data_path(*args)
|
43
|
+
self.sub_path("data", *args)
|
44
|
+
end
|
45
|
+
|
46
|
+
# returns:: [String] The full expanded path of the +lib+ directory below
|
47
|
+
# _root_dir_. All parameters passed in are joined onto the
|
48
|
+
# result. Trailing File::SEPARATOR is guaranteed if
|
49
|
+
# _*args_ are *not* present.
|
50
|
+
#
|
51
|
+
def self.lib_path(*args)
|
52
|
+
self.sub_path("lib", *args)
|
53
|
+
end
|
54
|
+
|
55
|
+
# returns:: [String] The full expanded path of the +ext+ directory below
|
56
|
+
# _root_dir_. All parameters passed in are joined onto the
|
57
|
+
# result. Trailing File::SEPARATOR is guaranteed if
|
58
|
+
# _*args_ are *not* present.
|
59
|
+
#
|
60
|
+
def self.ext_path(*args)
|
61
|
+
self.sub_path("ext", *args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.sub_path(sub,*args)
|
65
|
+
sp = ::File.join(root_dir, sub) + File::SEPARATOR
|
66
|
+
sp = ::File.join(sp, *args) if args
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
@@ -0,0 +1,131 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module Amalgalite
|
7
|
+
#
|
8
|
+
# A ProfileSampler is a sampler of profile times. It aggregates up profile
|
9
|
+
# events that happen for the same source. It is based upon the RFuzz::Sampler
|
10
|
+
# class from the rfuzz gem
|
11
|
+
#
|
12
|
+
class ProfileSampler
|
13
|
+
#
|
14
|
+
# create a new sampler with the given name
|
15
|
+
#
|
16
|
+
def initialize( name )
|
17
|
+
@name = name
|
18
|
+
reset!
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# reset the internal state so it may be used again
|
23
|
+
#
|
24
|
+
def reset!
|
25
|
+
@sum = 0.0
|
26
|
+
@sumsq = 0.0
|
27
|
+
@n = 0
|
28
|
+
@min = 0.0
|
29
|
+
@max = 0.0
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# add a sample to the calculations
|
34
|
+
#
|
35
|
+
def sample( value )
|
36
|
+
@sum += value
|
37
|
+
@sumsq += (value * value)
|
38
|
+
if @n == 0 then
|
39
|
+
@min = @max = value
|
40
|
+
else
|
41
|
+
@min = value if value < @min
|
42
|
+
@max = value if value > @max
|
43
|
+
end
|
44
|
+
@n += 1
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# return the mean of the data
|
49
|
+
#
|
50
|
+
def mean
|
51
|
+
@sum / @n
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# returns the standard deviation of the data
|
56
|
+
#
|
57
|
+
def stddev
|
58
|
+
begin
|
59
|
+
return 0.0 if ( 1 == @n )
|
60
|
+
Math.sqrt( (@sumsq - ( @sum * @sum / @n)) / (@n-1) )
|
61
|
+
rescue Errno::EDOM
|
62
|
+
return 0.0
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# return all the values as an array
|
68
|
+
#
|
69
|
+
def to_a
|
70
|
+
[ @name, @sum, @sumsq, @n, mean, stddev, @min, @max ]
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# return all the values as a hash
|
75
|
+
#
|
76
|
+
def to_h
|
77
|
+
{ 'name' => @name, 'n' => @n,
|
78
|
+
'sum' => @sum, 'sumsq' => @sumsq, 'mean' => mean,
|
79
|
+
'stddev' => stddev, 'min' => @min, 'max' => @max }
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# return a string containing the sampler summary
|
84
|
+
#
|
85
|
+
def to_s
|
86
|
+
"[%s] => sum: %d, sumsq: %d, n: %d, mean: %0.6f, stddev: %0.6f, min: %d, max: %d" % self.to_a
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# A Profile Tap recives +profile+ events from SQLite which involve the number of
|
93
|
+
# nanoseconds in wall-clock time it took for a particular thing to happen. In
|
94
|
+
# general this +thing+ is an SQL statement.
|
95
|
+
#
|
96
|
+
# It has a well known +profile+ method which when invoked will write the event
|
97
|
+
# to a delegate object.
|
98
|
+
#
|
99
|
+
#
|
100
|
+
class ProfileTap
|
101
|
+
|
102
|
+
attr_reader :samplers
|
103
|
+
|
104
|
+
#
|
105
|
+
# Create a new ProfileTap object that wraps the given object and calls the
|
106
|
+
# method named in +send_to+ ever time a profile event happens.
|
107
|
+
#
|
108
|
+
def initialize( wrapped_obj, send_to = 'profile' )
|
109
|
+
unless wrapped_obj.respond_to?( send_to )
|
110
|
+
raise Amalgalite::Error, "#{wrapped_obj.class.name} does not respond to #{send_to.to_s} "
|
111
|
+
end
|
112
|
+
|
113
|
+
@delegate_obj = wrapped_obj
|
114
|
+
@delegate_method = send_to
|
115
|
+
@samplers = {}
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Record the profile information and send the delegate object the msg and
|
120
|
+
# time information.
|
121
|
+
#
|
122
|
+
def profile( msg, time )
|
123
|
+
unless sampler = @samplers[msg]
|
124
|
+
msg = msg.gsub(/\s+/,' ')
|
125
|
+
sampler = @samplers[msg] = ProfileSampler.new( msg )
|
126
|
+
end
|
127
|
+
sampler.sample( time )
|
128
|
+
@delegate_obj.send( @delegate_method, msg, time )
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|