dm-cutie 0.3.16

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
File without changes
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2009, Cory ODaniel
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a
4
+ copy of this software and associated documentation files (the "Software"),
5
+ to deal in the Software without restriction, including without limitation
6
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ and/or sell copies of the Software, and to permit persons to whom the
8
+ Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19
+ DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,352 @@
1
+ Project Description
2
+ ===================
3
+ dm-cutie - The 'cutie' stands for Query Tracking. She is super thorough and easy as hell, just the way you like it.
4
+
5
+ Tracks queries that are executed by DataMapper. Models can be optionally excluded from tracking. Cutie currently only supports DataObjects Adapters.
6
+
7
+
8
+
9
+ Quickstart- The whole caboodle.
10
+ ===============================
11
+ There are two major pieces to the Cutie Project.
12
+
13
+ * dm-cutie - the query tracking portion - this gets installed 'in your app'
14
+ * dm-cutie-ui - A sinatra app that can connect to your dm-cutie repo and provide statisics.
15
+
16
+ Installing the query tracker (dm-cutie)
17
+ $ gem sources -a http://gemcutter.org # Add gem cutter to your gem sources
18
+ $ gem install dm-cutie
19
+ $ gem install dm-cutie-extras # This is a plugin pack for dm-cutie
20
+ $ gem install dm-cutie-ui # this is the front-end to dm-cutie
21
+
22
+
23
+ Adding it to your projects
24
+ # Somewhere after your DataMapper repositories are setup
25
+ require 'dm-cutie'
26
+ DataMapper::Cutie.enable_adapter :data_objects
27
+
28
+ # If you want some extras, sorry its Mysql centric, its what Im primarily familiar with
29
+ # require 'dm-cutie-extras'
30
+ #
31
+ # # Query Plan for Sqlite3
32
+ # DataMapper::Cutie::Extras.load :sqlite3_execution_step
33
+ #
34
+ # # Query Plan for Mysql
35
+ # DataMapper::Cutie::Extras.load :mysql_execution_step
36
+ #
37
+ # # Mysql Index information
38
+ # DataMapper::Cutie::Extras.load :mysql_index
39
+ #
40
+ # # Mysql warnings & errors
41
+ # DataMapper::Cutie::Extras.load :mysql_warning
42
+ #
43
+
44
+ DataMapper::Cutie.setup do |c|
45
+ # The name of the datamapper repository to store dm cutie tracked data in.
46
+ # If you are using something like mysql make sure that you created the schema
47
+ #
48
+ c[:repo_name] = :dm_cutie
49
+
50
+ # List of any models in YOUR application that you want dm-cutie to ignore
51
+ c[:exclude_models] = false #[:person, :car]
52
+
53
+ # List of models you want dm-cutie to specifically track
54
+ c[:only_models] = false #[:article, :address]
55
+
56
+ # What you consider a slow query
57
+ c[:slow_query_length] = 2
58
+ end
59
+
60
+ # passing true will tell dm-cutie to force a migration of all of HER models.
61
+ # she will always do a migration if she can't find her tables. In a development
62
+ # environment it is recommended to pass 'true' as your DataMapper models are probably
63
+ # changing often and there for should be re-profiled.
64
+ DataMapper::Cutie.start(true)
65
+
66
+ # If you are doing long term tracking in a production environment you will want to pass
67
+ # 'false' DM cutie will still migrate her tables the first time to make sure everything
68
+ # is set up correctly. PS, dm-cutie causes TONS of queries to be executed to adequatly track
69
+ # what is going on in your application. It would be recommended to only use her in development
70
+ # and staging environments.
71
+ # DataMapper::Cutie.start(false)
72
+
73
+ Starting the dm-cutie-ui sinatra app
74
+ # A bin file should have been included with the gem
75
+ #
76
+ # Want to see options?
77
+ # dm-cutie-ui --help
78
+ #
79
+ # The default port is 4567
80
+ $ dm-cutie-ui
81
+
82
+ Log into dm-cutie-ui
83
+
84
+ * Go to http://localhost:4567 #or whatever port you used
85
+ * Enter the path to your dm-cutie repo # mysql://root@localhost/dm-cutie
86
+ * Click 'connect' or enter a password if the account you are connecting with needs a password
87
+ * Learn?
88
+
89
+
90
+ How It Works
91
+ ============
92
+ DM Cutie overrides DataObjectsAdapters and puts in some hooks to track queries. Cutie uses four models to store information about queries
93
+
94
+ * GeneralizedQuery - This is a query without variables bound to it
95
+ * Example: Person.first(:email => "user@example.com") would be stored as "SELECT * FROM `people` WHERE `email` = ?"
96
+ * This query will be unique to this table.
97
+ * Useful for determine how many generic queries are being generated by your application
98
+ * ExecutedQuery - This is a query WITH variables bound to it
99
+ * Example: Person.first(:email => "user@example.com") would be stored as "SELECT * FROM `people` WHERE `email` = 'user@example.com'"
100
+ * EVERY executed statement is stored in this table, executed statements do not have a unique constraint.
101
+ * This table has a foreign Key to GeneralizedQuery, so you can determine which ExecutedQuery objects are related
102
+ * Example: Person.first(:email => "user@example.com") and Person.first(:email => "person@example.com")
103
+ * Both ExecutedQuery objects would relate back to the GeneralizedQuery, Person.first(:email => ?) ("SELECT * FROM `people` WHERE `email` = ?")
104
+ * This also stores the line number and file name of the caller.
105
+ * RepositoryStorage - This keeps track of all storages (tables) across repositories.
106
+ * UnboundQueries 'belong' to RepositoryStorages through QueryStorageLink
107
+ * Example:
108
+ * if you have a Person model that is stored in a YAML repository that would be one RepositoryStorage
109
+ * if you have a Person model that is stored in a MySQL repostiroy that would be an additional RepositoryStorage
110
+ * UnboundQueries directed at MySQL for the Person model would be related to the MySQL Person RepositoryStorage object
111
+ * QueryStorageLink - ties a RepositoryStorage
112
+ * Denotes whether a RepositoryStorage was the 'primary storage' for a query
113
+ * The primary storage is determine by the Repository#name and Repository#adapter of the query object
114
+ * Other QueryStorageLinks on an GeneralizedQuery are a result of relationships that are stored in the Query#links
115
+
116
+
117
+ Important
118
+ =========
119
+ DM Cutie generates a ton of queries itself to track what is going on in DataMapper. It would be wise to use it to only to track your queries in a Staging or Development environment or temporarily in production.
120
+
121
+ DM Cutie essentially records a 'snapshot' of everything that happened in your applications repository.
122
+
123
+
124
+ Usage
125
+ =======
126
+ require 'dm-cutie'
127
+
128
+ # Load the hook for the adapter you want to track
129
+ DataMapper::Cutie.enable_adapter :data_objects
130
+
131
+ #note, these haven't been written yet
132
+ # DataMapper::Cutie.enable_adapter :yaml
133
+ # DataMapper::Cutie.enable_adapter :in_memory
134
+
135
+ # Optional
136
+ DataMapper::Cutie.setup do |c|
137
+ # The repo to use to store Cutie's data in
138
+ c[:repo_name] = :dm_cutie
139
+
140
+ # Models to always exclude from tracking
141
+ c[:exclude_models] = false #[:person, :car]
142
+
143
+ # Models to only tracker
144
+ c[:only_models] = false #[:article, :address]
145
+
146
+ # Time in seconds you consider a query to be slow
147
+ c[:slow_query_length] = 2
148
+ end
149
+
150
+ DataMapper::Cutie.start
151
+
152
+
153
+ Adapter Trackers vs Tracker Hooks
154
+ ====================================
155
+
156
+ * Adapter Trackers are the basic trackers for a specific adapter (DataObjectsAdapter, MySQLAdapter, etc)
157
+ * Provide the basic functionality to 'capture' a query in progress and the basic storage of details in ExecutedQuery, GeneralizedQuery, RepositoryStorage, and QueryStorageLink
158
+ * Currently this is NOT DRY and is a bit hostile, as it literally reimplements the CRUD layer methods in DO
159
+ * Tracker Hooks allow you to create additional storages for details that are outside the basic 4 models included in Cutie
160
+ * Example: All SQL queries have a RepositoryStorage (the tables in the FROM statement), but only a MySQL query would have a MySQL execution plan, so you can have a Tracker Hook that will only store MySQL Execution plans if its the mysql adapter
161
+ * Allow you to perform logic on an 'executed query'
162
+
163
+
164
+ Enabling Tracker Hooks
165
+ =========================
166
+
167
+ A project of tracker hooks exists called dm-cutie-extras.
168
+
169
+ require 'dm-cutie'
170
+ DataMapper::Cutie.enable_adapter :data_objects
171
+
172
+ # Simply require the additional trackers
173
+ require 'dm-cutie-extras'
174
+ DataMapper::Cutie::Extras.load :mysql_execution_step
175
+ DataMapper::Cutie::Extras.load :sqlite3_execution_step
176
+
177
+ DataMapper::Cutie.start(true)
178
+
179
+
180
+ Currently included are:
181
+
182
+ * MySQL Execution Step - Runs a query plan for MySQL queries
183
+ * Sqlite3 Execution Step - Runs a query plan for Sqlite3 queries
184
+
185
+
186
+ Writing Tracker Hooks
187
+ =========================
188
+ To write a tracker simply include DataMapper::Cutie::Tracker::Hook::Abstract into a class and define a few methods
189
+ class MyTrackerHook
190
+ include DataMapper::Cutie::Tracker::Hook::Abstract
191
+
192
+ # The name of your hook
193
+ def self.hook_name
194
+ "Really cool hook"
195
+ end
196
+
197
+ # the statements this tracker applies to
198
+ def self.supported_statements
199
+ [:select]
200
+ end
201
+
202
+ # The adapters this tracker applies to
203
+ def self.suppored_adapters
204
+ [:mysql]
205
+ end
206
+
207
+ # This is a factory method that will run your tracking logic
208
+ # this method will receive the current ExecutedQuery
209
+ #
210
+ def self.track( executed_query )
211
+ # Your tracking logic goes here
212
+ end
213
+ end
214
+
215
+ Then let Cutie know she has a new hook to work with
216
+ DataMapper::Cutie.add_tracker_hook :my_tracker_hook #Symbol representation of your class name
217
+
218
+ Example "Hi Mom! Tracker Hook" - Sends mom an email telling her what SELECT statements where ran
219
+
220
+ class HiMomTrackerHook
221
+ include DataMapper::Cutie::Tracker::Hook::Abstract
222
+
223
+ # The name of your hook
224
+ def self.hook_name
225
+ "Hi Mom! Tracker hook"
226
+ end
227
+
228
+ # the statements this tracker applies to
229
+ def self.supported_statements
230
+ [:select]
231
+ end
232
+
233
+ # The adapters this tracker applies to
234
+ def self.suppored_adapters
235
+ [:mysql]
236
+ end
237
+
238
+ def self.track( executed_query )
239
+ # This is an absolutely awesome tracker, because my mom can optimize the heck out of a query.
240
+
241
+ my_cool_mail_method(
242
+ :to => "mom@example.com",
243
+ :from => "yourlovingchild@example.com",
244
+ :subject => "my friends are reading my data",
245
+ :body => "My friend just executed this query: #{executed_query.generalized_query.statement}"
246
+ )
247
+ end
248
+ end
249
+
250
+ # Of course tell cutie she has a new hook
251
+ DataMapper::Cutie.add_tracker_hook :hi_mom_tracker_hook
252
+
253
+
254
+ Example "Which Server Executed What" - Keeps track of what Application server sent the query
255
+ # Create a model to store server names in
256
+ class QueryingServer
257
+ require 'socket'
258
+ include DataMapper::Cutie::Tracker::Hook::Abstract
259
+
260
+ include DataMapper::Resource
261
+ has n, :executed_queries
262
+
263
+ property :id, Serial
264
+ property :name, String, :unique_index => true
265
+
266
+ def self.hook_name
267
+ "Which Server Executed A Query Hook"
268
+ end
269
+
270
+ def self.supported_statements
271
+ [:select, :insert, :update, :delete]
272
+ end
273
+
274
+ def self.suppored_adapters
275
+ [:mysql]
276
+ end
277
+
278
+ def self.track( executed_query )
279
+ DataMapper::Cutie.repo do #get access to the Cutie's repo (you can store this wherever, probably makes sense here though)
280
+
281
+ trans = DataMapper::Transaction.new #start a transaction (optional)
282
+
283
+ trans.link do
284
+ querying_server = QueryingServer.first_or_create(:name => Socket.gethostname)
285
+ querying_server.executed_queries << executed_query
286
+ querying_server.save
287
+ end
288
+
289
+ end
290
+ end
291
+
292
+ end
293
+
294
+ # Let ExecutedQuery know its getting a new column :P
295
+ ExecutedQuery.send :belongs_to, :querying_server
296
+
297
+ # Of course let cutie know she has a new hook
298
+ DataMapper::Cutie.add_tracker_hook :querying_server
299
+
300
+ # Here we have one other concern, we've created a new model: QueryingServer. We want Cutie to treat this model as an internal model, so she doesn't track it (which would cause an infinite loop)
301
+ DataMapper::Cutie.add_internal_model :querying_server
302
+
303
+
304
+ Tracker hooks don't have to be a model, but they can be.
305
+
306
+ The #track method receives a ExecutedQuery object. Here are some methods available on that object
307
+
308
+ executed_query.generalized_query #The GeneralizedQuery object
309
+ executed_query.statement #The bound SQL Statement
310
+ executed_query.generalized_query.statement #The generalized SQL Statement
311
+ executed_query.bind_values #The bind values for the SQL Statement
312
+ # etc... Everything that is available on a DataMapper::Query object
313
+
314
+
315
+ Writing an Adapter Tracker
316
+ ==================
317
+
318
+ Trackers should 'hook' around the method that is to be tracked. In the DataObjectsTracker example
319
+ the methods being tracked are
320
+
321
+ * select_statement
322
+ * insert_statement
323
+ * update_statement
324
+ * delete_statement
325
+
326
+ A model that is performing a crud action will only be tracked if the adapter of the repository that the action
327
+ is happening in is based on the DO Adapter.
328
+
329
+
330
+ TODOS
331
+ =====
332
+ * Model.all => Doesnt track -> Probably need to add an override to Collection class
333
+ * Rspecs, convert test_script.rb to specs
334
+
335
+ * Format documentation for rdoc/yard whatever.
336
+ * is there an easier way to 'hi jack' something at the DO Adapter level other than making the code unDRY in /tracker/data_objects/overrides
337
+ * Tried using extlib hooks, but needed access to local variables in the DO CRUD methods
338
+
339
+ * Namespace the Models so they dont interfere with anyones code if they have the same model names.
340
+ * Tracker hooks trackers should automatically be stored in DataMapper::Cutie.repo instead of having to explicitly call it
341
+ * Spot Tracking => Cutie.track{...statements...} #So you can track a specific area, rather than the whole system
342
+
343
+ CONSIDERATIONS
344
+ ===============
345
+
346
+ * Should this be pushed back to the DO level?
347
+ * I like using DM to access data... also the potential to track DM Sphinx, etc
348
+ * Its highly geared toward SQL, can it be abstracted
349
+ * use CRUD operations instead of :select, :insert, :update, :delete
350
+ * Get self.default_respository_name to work so all CUTIE queries dont have to be done in Cutie.repo block
351
+ * class_eval def self.default_repository_name; #{THE_NAME}; end;
352
+ * Add ability to exclude/include repositories. You might have 3 MySQL repos, and are tracking the DO adapter, but are only interested in a particular database
data/Rakefile ADDED
@@ -0,0 +1,103 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require "extlib"
6
+
7
+ require 'rake'
8
+ require 'rake/clean'
9
+ require 'spec/rake/spectask'
10
+ require "spec"
11
+ require 'rake/rdoctask'
12
+ require 'rake/gempackagetask'
13
+
14
+ ROOT = Pathname(__FILE__).dirname.expand_path
15
+ require ROOT + 'lib/dm-cutie/version'
16
+
17
+ @spec = Gem::Specification.new do |s|
18
+ s.name = %q{dm-cutie}
19
+ s.version = DataMapper::Cutie::VERSION
20
+
21
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
22
+ s.authors = ["Cory ODaniel"]
23
+ s.date = %q{2009-10-15}
24
+ s.summary = %q{DataMapper's Original Query Tracker'}
25
+ s.description = %q{DataMapper's Query Tracker; She is super thorough and easy as hell, just the way you like it.}
26
+
27
+ s.email = %q{cutie@coryodaniel.com}
28
+
29
+ s.extra_rdoc_files = ["README.markdown", "LICENSE", "History.txt"]
30
+ s.files = ["LICENSE", "README.markdown", "Rakefile",
31
+ "lib/dm-cutie.rb",
32
+ "lib/dm-cutie/cutie.rb",
33
+ "lib/dm-cutie/version.rb",
34
+
35
+ "lib/dm-cutie/models/executed_query.rb",
36
+ "lib/dm-cutie/models/query_storage_link.rb",
37
+ "lib/dm-cutie/models/repository_storage.rb",
38
+ "lib/dm-cutie/models/generalized_query.rb",
39
+
40
+ "lib/dm-cutie/tracker/data_objects.rb",
41
+ "lib/dm-cutie/tracker/data_objects/helper.rb",
42
+ "lib/dm-cutie/tracker/data_objects/overrides.rb",
43
+
44
+ "lib/dm-cutie/tracker/hook/abstract.rb"
45
+
46
+ ]
47
+ s.add_dependency "extlib",">=0.9.12"
48
+ s.add_dependency "dm-core", '>=0.10.0'
49
+ s.add_dependency "dm-types", '>=0.10.0'
50
+ s.has_rdoc = true
51
+ s.homepage = "http://github.com/coryodaniel/dm-cutie"
52
+ s.require_paths = ["lib"]
53
+ s.rubygems_version = %q{1.2.0}
54
+ end
55
+
56
+ [ ROOT, ROOT.parent ].each do |dir|
57
+ Pathname.glob(dir.join('tasks/**/*.rb').to_s).each { |f| require f }
58
+ end
59
+
60
+ NAME = @spec.name
61
+ GEM_VERSION = DataMapper::Cutie::VERSION
62
+
63
+ Rake::GemPackageTask.new(@spec) do |pkg|
64
+ pkg.gem_spec = @spec
65
+ end
66
+
67
+ desc "install the plugin locally"
68
+ task :install => [:package] do
69
+ sh %{sudo gem install #{install_home} pkg/#{NAME}-#{GEM_VERSION} --no-update-sources}
70
+ end
71
+
72
+ desc "create a gemspec file"
73
+ task :make_spec do
74
+ File.open("#{NAME}.gemspec", "w") do |file|
75
+ file.puts @spec.to_ruby
76
+ end
77
+ end
78
+
79
+ Rake::RDocTask.new do |rdoc|
80
+ files = ["README.markdown", "History.txt", "LICENSE", "lib/**/*.rb"]
81
+ rdoc.rdoc_files.add(files)
82
+ rdoc.main = "README.markdown"
83
+ rdoc.title = "DM Cutie"
84
+
85
+ rdoc.rdoc_dir = "doc/rdoc"
86
+ rdoc.options << "--line-numbers" << "--inline-source"
87
+ end
88
+
89
+ Spec::Rake::SpecTask.new do |t|
90
+ t.spec_files = Dir["./spec/**/*_spec.rb"]
91
+ t.spec_files.unshift './spec/spec_helper.rb'
92
+
93
+ t.libs = ['lib']
94
+ t.spec_opts << "--color" << "--format" << "specdoc" #"progress"
95
+
96
+ if ENV['RCOV']
97
+ t.rcov = true
98
+ t.rcov_opts << '--exclude' << 'pkg,spec,interactive.rb,install_test_suite.rb,lib/gems,' + Gem.path.join(',')
99
+ t.rcov_opts << '--text-summary'
100
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
101
+ t.rcov_opts << '--only-uncovered'
102
+ end
103
+ end