piggly-nsd 2.3.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.
Files changed (96) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +170 -0
  3. data/Rakefile +33 -0
  4. data/bin/piggly +8 -0
  5. data/lib/piggly/command/base.rb +148 -0
  6. data/lib/piggly/command/report.rb +162 -0
  7. data/lib/piggly/command/trace.rb +90 -0
  8. data/lib/piggly/command/untrace.rb +78 -0
  9. data/lib/piggly/command.rb +8 -0
  10. data/lib/piggly/compiler/cache_dir.rb +119 -0
  11. data/lib/piggly/compiler/coverage_report.rb +63 -0
  12. data/lib/piggly/compiler/trace_compiler.rb +117 -0
  13. data/lib/piggly/compiler.rb +7 -0
  14. data/lib/piggly/config.rb +80 -0
  15. data/lib/piggly/dumper/index.rb +121 -0
  16. data/lib/piggly/dumper/qualified_name.rb +36 -0
  17. data/lib/piggly/dumper/qualified_type.rb +141 -0
  18. data/lib/piggly/dumper/reified_procedure.rb +172 -0
  19. data/lib/piggly/dumper/skeleton_procedure.rb +112 -0
  20. data/lib/piggly/dumper.rb +9 -0
  21. data/lib/piggly/installer.rb +137 -0
  22. data/lib/piggly/parser/grammar.tt +748 -0
  23. data/lib/piggly/parser/nodes.rb +378 -0
  24. data/lib/piggly/parser/traversal.rb +50 -0
  25. data/lib/piggly/parser/treetop_ruby19_patch.rb +21 -0
  26. data/lib/piggly/parser.rb +69 -0
  27. data/lib/piggly/profile.rb +108 -0
  28. data/lib/piggly/reporter/base.rb +106 -0
  29. data/lib/piggly/reporter/html_dsl.rb +63 -0
  30. data/lib/piggly/reporter/index.rb +114 -0
  31. data/lib/piggly/reporter/procedure.rb +129 -0
  32. data/lib/piggly/reporter/resources/highlight.js +38 -0
  33. data/lib/piggly/reporter/resources/piggly.css +515 -0
  34. data/lib/piggly/reporter/resources/sortable.js +493 -0
  35. data/lib/piggly/reporter.rb +8 -0
  36. data/lib/piggly/tags.rb +280 -0
  37. data/lib/piggly/task.rb +215 -0
  38. data/lib/piggly/util/blankslate.rb +114 -0
  39. data/lib/piggly/util/cacheable.rb +19 -0
  40. data/lib/piggly/util/enumerable.rb +44 -0
  41. data/lib/piggly/util/file.rb +17 -0
  42. data/lib/piggly/util/process_queue.rb +96 -0
  43. data/lib/piggly/util/thunk.rb +39 -0
  44. data/lib/piggly/util.rb +9 -0
  45. data/lib/piggly/version.rb +15 -0
  46. data/lib/piggly.rb +20 -0
  47. data/spec/examples/compiler/cacheable_spec.rb +190 -0
  48. data/spec/examples/compiler/report_spec.rb +25 -0
  49. data/spec/examples/compiler/trace_spec.rb +123 -0
  50. data/spec/examples/config_spec.rb +63 -0
  51. data/spec/examples/dumper/index_spec.rb +199 -0
  52. data/spec/examples/dumper/procedure_spec.rb +116 -0
  53. data/spec/examples/grammar/expression_spec.rb +302 -0
  54. data/spec/examples/grammar/statements/assignment_spec.rb +70 -0
  55. data/spec/examples/grammar/statements/declaration_spec.rb +21 -0
  56. data/spec/examples/grammar/statements/exception_spec.rb +78 -0
  57. data/spec/examples/grammar/statements/if_spec.rb +191 -0
  58. data/spec/examples/grammar/statements/loop_spec.rb +41 -0
  59. data/spec/examples/grammar/statements/sql_spec.rb +71 -0
  60. data/spec/examples/grammar/tokens/comment_spec.rb +58 -0
  61. data/spec/examples/grammar/tokens/datatype_spec.rb +58 -0
  62. data/spec/examples/grammar/tokens/identifier_spec.rb +74 -0
  63. data/spec/examples/grammar/tokens/keyword_spec.rb +44 -0
  64. data/spec/examples/grammar/tokens/label_spec.rb +40 -0
  65. data/spec/examples/grammar/tokens/literal_spec.rb +30 -0
  66. data/spec/examples/grammar/tokens/lval_spec.rb +50 -0
  67. data/spec/examples/grammar/tokens/number_spec.rb +34 -0
  68. data/spec/examples/grammar/tokens/sqlkeywords_spec.rb +45 -0
  69. data/spec/examples/grammar/tokens/string_spec.rb +54 -0
  70. data/spec/examples/grammar/tokens/whitespace_spec.rb +40 -0
  71. data/spec/examples/installer_spec.rb +59 -0
  72. data/spec/examples/parser/nodes_spec.rb +73 -0
  73. data/spec/examples/parser/traversal_spec.rb +14 -0
  74. data/spec/examples/parser_spec.rb +118 -0
  75. data/spec/examples/profile_spec.rb +153 -0
  76. data/spec/examples/reporter/html/dsl_spec.rb +0 -0
  77. data/spec/examples/reporter/html/index_spec.rb +0 -0
  78. data/spec/examples/reporter/html_spec.rb +1 -0
  79. data/spec/examples/reporter_spec.rb +0 -0
  80. data/spec/examples/tags_spec.rb +285 -0
  81. data/spec/examples/task_spec.rb +0 -0
  82. data/spec/examples/util/cacheable_spec.rb +41 -0
  83. data/spec/examples/util/enumerable_spec.rb +64 -0
  84. data/spec/examples/util/file_spec.rb +40 -0
  85. data/spec/examples/util/process_queue_spec.rb +16 -0
  86. data/spec/examples/util/thunk_spec.rb +59 -0
  87. data/spec/examples/version_spec.rb +0 -0
  88. data/spec/issues/007_spec.rb +25 -0
  89. data/spec/issues/008_spec.rb +73 -0
  90. data/spec/issues/018_spec.rb +25 -0
  91. data/spec/issues/028_spec.rb +48 -0
  92. data/spec/issues/032_spec.rb +98 -0
  93. data/spec/issues/036_spec.rb +41 -0
  94. data/spec/spec_helper.rb +312 -0
  95. data/spec/spec_suite.rb +5 -0
  96. metadata +162 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b53364d89c087bf4c2ce49ec451ece2cb1c17694bab9e765e034a05493ef3999
4
+ data.tar.gz: a46eea287041eeaa4130b730dd409f1f35663e6fca5258e96c639b1f567eeb11
5
+ SHA512:
6
+ metadata.gz: 1cfd022ed8e03c56d372911943bd7b662a6ff967f63bde7b09a01e010b5117bc41ad0f5c245a30e72139776a5eece380ab9d2d60175462555a874cfb008e73c8
7
+ data.tar.gz: b8653bb06b92d31eee24e5c9a317fe912dc61e01d46fae649dadeb4610b831f4ad50199683996cddc756727124be68514fc0d919b372d80f70347851539d7751
data/README.md ADDED
@@ -0,0 +1,170 @@
1
+ [![CI](https://github.com/sergeiboikov/piggly/actions/workflows/ci.yml/badge.svg)](https://github.com/sergeiboikov/piggly/actions/workflows/ci.yml)
2
+ # Piggly-NSD
3
+
4
+ Code coverage reports for PostgreSQL PL/pgSQL stored procedures
5
+
6
+ **Note:** This is a maintained fork of the original [piggly](https://github.com/kputnam/piggly) project by Kyle Putnam.
7
+
8
+ ![Screenshot](https://kputnam.github.io/piggly/images/example.png)
9
+
10
+ ## Purpose
11
+
12
+ PL/pgSQL doesn't have much in the way of developer tools, and writing automated tests for
13
+ stored procedures can be much easier when you know what you haven't tested. Code coverage
14
+ allows you to see which parts of your code haven't been executed.
15
+
16
+ Piggly is a tool (written in Ruby, but you can write your tests in any language) to track
17
+ code coverage of PostgreSQL PL/pgSQL stored procedures. It reports on code coverage to help
18
+ you identify untested parts of your code.
19
+
20
+ ## How Does It Work?
21
+
22
+ Piggly tracks the execution of PostgreSQL's PL/pgSQL stored procedures by recompiling
23
+ the stored procedure with instrumentation code. Basically, RAISE WARNING statements notify the
24
+ client of an execution event (e.g., a branch condition evaluating to true or false). It records
25
+ these events and generates prettified source code that is annotated with coverage details.
26
+
27
+ ## Features
28
+
29
+ * Readable and easily-navigable reports (see [example](http://kputnam.github.io/piggly/reports/index.html))
30
+ * Language agnostic - write your tests in Ruby, Python, Java, SQL scripts etc
31
+ * Branch, block, and loop coverage analysis
32
+ * Instrumenting source-to-source compiler
33
+ * Low test execution overhead
34
+ * Reduced compilation times by use of disk caching
35
+ * Possible to aggregate coverage across multiple runs
36
+
37
+ ## Limitations
38
+
39
+ * Not all PL/pgSQL grammar is currently supported, but the grammar is easy to modify
40
+ * Cannot parse nested dollar-quoted strings, eg $A$ ... $B$ ... $B$ ... $A$
41
+ * SQL statements are not instrumented, so their branches (COALESCE, WHERE-clauses, etc) aren't tracked
42
+
43
+ ## Requirements
44
+
45
+ * [Treetop](http://github.com/nathansobo/treetop): `gem install treetop`
46
+ * The [ruby-pg driver](http://bitbucket.org/ged/ruby-pg/): `gem install pg`
47
+ * The examples require ActiveRecord: `gem install activerecord`
48
+
49
+ ## Versioning
50
+
51
+ This fork continues the version numbering from the original piggly project:
52
+ - **2.3.1** - Last version of original piggly by Kyle Putnam
53
+ - **2.3.2** - NSD fork with UTF-8 encoding support and updated dependencies
54
+
55
+ ## How to Install
56
+
57
+ To install the latest from github:
58
+
59
+ $ git clone https://github.com/sergeiboikov/piggly.git
60
+ $ cd piggly
61
+ $ bundle install
62
+ $ bundle exec rake spec
63
+
64
+ $ gem uninstall piggly-nsd # If piggly-nsd was installed earlier
65
+ $ gem build piggly.gemspec
66
+ $ gem install piggly-nsd-*.gem
67
+
68
+ To install the latest release:
69
+
70
+ $ gem install piggly-nsd
71
+
72
+ ## Usage
73
+
74
+ Your stored procedures must already be loaded in the database. Configure your database connection in
75
+ a file named `config/database.yml` relative to where you want to run piggly. You can also specify the
76
+ `-d PATH` to an existing configuration file. The contents of the file follow ActiveRecord conventions:
77
+
78
+ piggly:
79
+ adapter: postgresql
80
+ database: cookbook
81
+ username: kputnam
82
+ password: secret
83
+ host: localhost
84
+
85
+ Here we'll add some stored procedures to the database we described above:
86
+
87
+ $ cat example/proc/*.sql | psql -U kputnam -h localhost cookbook
88
+
89
+ Note the connection is expected to be named `piggly` but you may specify the `-k DATABASE` option to
90
+ use a different connection name (eg `-k development` in Rails). See also `example/config/database.yml`.
91
+
92
+ Now you are ready to recompile and install your stored procedures. This reads the configuration from
93
+ `./config/database.yml` relative to the current working directory.
94
+
95
+ $ piggly trace
96
+ compiling 5 procedures
97
+ Compiling scramble
98
+ Compiling scramble
99
+ Compiling numberedargs
100
+ Compiling snippets
101
+ Compiling iterate
102
+ tracing 5 procedures
103
+
104
+ For running the development version use the following command:
105
+ `ruby bin/piggly trace`
106
+
107
+ This caches the original version (without instrumentation) in `piggly/cache` so you can restore them
108
+ later. Piggly will only recompile procedures that have changed in the database since it last
109
+ made a copy in `piggly/cache`.
110
+
111
+ *WARNING*: piggly fetches your code from the database and replaces it (in the database) with the
112
+ instrumented code. If you run `piggly trace` twice consecutively, the second time will cause an error
113
+ because you are trying to re-instrument code that has already been instrumented. You need to run
114
+ `piggly untrace` or restore your original stored procedures manually before you can trace them again.
115
+
116
+ Now you're ready to execute your tests. Make sure your connection is configured to log `RAISE WARNING`
117
+ messages to a file -- or you can log them to `STDERR` and redirect that to a file. For instance you
118
+ might run:
119
+
120
+ $ ant test 2> messages.txt
121
+ $ make test 2> messages.txt
122
+ etc.
123
+
124
+ To build the coverage report, have piggly read that file in by executing `piggly report < messages.txt`,
125
+ or `piggly report -f messages.txt`. You don't actually need the intermediate file, you can pipe your
126
+ test suite directly in like `ant test 2>&1 | piggly report`.
127
+
128
+ Once the report is built you can open it in `piggly/reports/index.html`.
129
+
130
+ ## Running the Examples
131
+
132
+ $ cd piggly
133
+ $ bundle install
134
+ $ cat example/README
135
+ ...
136
+
137
+ $ ./example/run-tests
138
+ compiling 5 procedures
139
+ Compiling scramble
140
+ Compiling scramble
141
+ Compiling numberedargs
142
+ Compiling snippets
143
+ Compiling iterate
144
+ tracing 5 procedures
145
+ Loaded suite /home/kputnam/wd/piggly/example/test/iterate_test
146
+ Started
147
+ ......
148
+ Finished in 0.199236 seconds.
149
+
150
+ 6 tests, 6 assertions, 0 failures, 0 errors, 0 skips
151
+
152
+ Test run options: --seed 25290
153
+ clearing previous coverage
154
+ storing coverage profile
155
+ creating index
156
+ creating reports
157
+ reporting coverage for scramble
158
+ reporting coverage for scramble
159
+ reporting coverage for numberedargs
160
+ reporting coverage for snippets
161
+ reporting coverage for iterate: +0.0% block, +0.0% branch, +0.0% loop
162
+ restoring 5 procedures
163
+ OK, view /home/kputnam/wd/piggly/example/piggly/reports/index.html
164
+
165
+ $ ls -alh example/reports/index.html
166
+ -rw-r--r-- 1 kputnam kputnam 1.4K 2010-04-28 11:21 example/reports/index.html
167
+
168
+ ## Bugs & Issues
169
+
170
+ Please report any issues or feature requests on the [github tracker](https://github.com/sergeiboikov/piggly/issues).
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ begin
2
+ require "rubygems"
3
+ require "bundler/setup"
4
+ rescue LoadError
5
+ warn "couldn't load bundler:"
6
+ warn " #{$!}"
7
+ end
8
+
9
+ begin # rspec-2
10
+ require "rspec/core/rake_task"
11
+ RSpec::Core::RakeTask.new do |t|
12
+ t.verbose = false
13
+ t.pattern = "spec/**/*_spec.rb"
14
+ t.rspec_opts = "--color --format=p"
15
+ end
16
+ rescue LoadError => first
17
+ begin # rspec-1
18
+ require "spec/rake/spectask"
19
+ Spec::Rake::SpecTask.new do |t|
20
+ t.pattern = "spec/**/*_spec.rb"
21
+ t.spec_opts << "--color"
22
+ t.spec_opts << "--format=p"
23
+ end
24
+ rescue LoadError => second
25
+ task :spec do
26
+ warn "couldn't load rspec version 1 or 2:"
27
+ warn " #{first}"
28
+ warn " #{second}"
29
+ end
30
+ end
31
+ end
32
+
33
+ task :default => :spec
data/bin/piggly ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), *%w(.. lib)))
3
+ require "piggly"
4
+
5
+ # Number of children to fork for parallel tasks
6
+ Piggly::Util::ProcessQueue.concurrent = 2
7
+
8
+ Piggly::Command::Base.main(ARGV)
@@ -0,0 +1,148 @@
1
+ module Piggly
2
+ module Command
3
+
4
+ class Base
5
+ end
6
+
7
+ class << Base
8
+
9
+ def main(argv)
10
+ cmd, argv = command(argv)
11
+
12
+ if cmd.nil?
13
+ abort "usage: #{$0} {report|trace|untrace} --help"
14
+ else
15
+ cmd.main(argv)
16
+ end
17
+ end
18
+
19
+ # @return [(Class, Array<String>)]
20
+ def command(argv)
21
+ return if argv.empty?
22
+ head, *tail = argv
23
+
24
+ case head.downcase
25
+ when "report"; [Report, tail]
26
+ when "trace"; [Trace, tail]
27
+ when "untrace"; [Untrace, tail]
28
+ end
29
+ end
30
+
31
+ # @return [PG::Connection]
32
+ def connect(config)
33
+ require "pg"
34
+ require "erb"
35
+
36
+ files = Array(config.database_yml ||
37
+ %w(piggly/database.yml
38
+ config/database.yml
39
+ piggly/database.json
40
+ config/database.json))
41
+
42
+ path = files.find{|x| File.exist?(x) } or
43
+ raise "No database config files found: #{files.join(", ")}"
44
+
45
+ specs =
46
+ if File.extname(path) == ".json"
47
+ require "json"
48
+ JSON.load(ERB.new(IO.read(path)).result)
49
+ else
50
+ require "yaml"
51
+ YAML.unsafe_load(ERB.new(IO.read(path)).result)
52
+ end
53
+
54
+ spec = (specs.is_a?(Hash) and specs[config.connection_name]) or
55
+ raise "Database '#{config.connection_name}' is not configured in #{path}"
56
+
57
+ conn = PG::Connection.connect(spec["host"], spec["port"], nil, nil,
58
+ spec["database"], spec["username"], spec["password"])
59
+
60
+ # Set client encoding to UTF-8 to properly handle Cyrillic and other Unicode characters
61
+ conn.set_client_encoding('UTF8')
62
+
63
+ conn
64
+ end
65
+
66
+ # @return [Enumerable<SkeletonProcedure>]
67
+ def filter(config, index)
68
+ if config.filters.empty?
69
+ index.procedures
70
+ else
71
+ head, _ = config.filters
72
+
73
+ start =
74
+ case head.first
75
+ when :+; []
76
+ when :-; index.procedures
77
+ end
78
+
79
+ config.filters.inject(start) do |s, pair|
80
+ case pair.first
81
+ when :+; s | index.procedures.select(&pair.last)
82
+ when :-; s.reject(&pair.last)
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def o_accumulate(config)
89
+ lambda{|x| config.accumulate = x }
90
+ end
91
+
92
+ def o_cache_root(config)
93
+ lambda{|x| config.cache_root = x }
94
+ end
95
+
96
+ def o_report_root(config)
97
+ lambda{|x| config.report_root = x }
98
+ end
99
+
100
+ def o_include_paths(config)
101
+ lambda{|x| config.include_paths.concat(x.split(":")) }
102
+ end
103
+
104
+ def o_database_yml(config)
105
+ lambda{|x| config.database_yml = x }
106
+ end
107
+
108
+ def o_connection_name(config)
109
+ lambda{|x| config.connection_name = x }
110
+ end
111
+
112
+ def o_version(config)
113
+ lambda {|x| puts "piggly #{VERSION} #{VERSION::RELEASE_DATE}"; exit! }
114
+ end
115
+
116
+ def o_dry_run(config)
117
+ lambda {|x| config.dry_run = true }
118
+ end
119
+
120
+ def o_select(config)
121
+ lambda do |x|
122
+ filter =
123
+ if m = x.match(%r{^/([^/]+)/$})
124
+ lambda{|p| p.name.to_s.match(m.captures.first) }
125
+ else
126
+ lambda{|p| p.name.to_s === x }
127
+ end
128
+
129
+ config.filters << [:+, filter]
130
+ end
131
+ end
132
+
133
+ def o_reject(config)
134
+ lambda do |x|
135
+ filter =
136
+ if m = x.match(%r{^/([^/]+)/$})
137
+ lambda{|p| p.name.to_s.match(m.captures.first) }
138
+ else
139
+ lambda{|p| p.name.to_s === x }
140
+ end
141
+
142
+ config.filters << [:-, filter]
143
+ end
144
+ end
145
+ end
146
+
147
+ end
148
+ end
@@ -0,0 +1,162 @@
1
+ module Piggly
2
+ module Command
3
+
4
+ #
5
+ # This command reads a given file (or STDIN) which is expected to contain messages like the
6
+ # pattern Profile::PATTERN, which is probbaly "WARNING: PIGGLY 0123456789abcdef".
7
+ #
8
+ # Lines in the input that match this pattern are profiled and used to generate a report
9
+ #
10
+ class Report < Base
11
+ end
12
+
13
+ class << Report
14
+ def main(argv)
15
+ require "pp"
16
+ io, config = configure(argv)
17
+
18
+ profile = Profile.new
19
+ index = Dumper::Index.new(config)
20
+
21
+ procedures = filter(config, index)
22
+
23
+ if procedures.empty?
24
+ if filters.empty?
25
+ abort "no stored procedures in the cache"
26
+ else
27
+ abort "no stored procedures in the cache matched your criteria"
28
+ end
29
+ elsif config.dry_run?
30
+ puts procedures.map{|p| p.signature }
31
+ exit 0
32
+ end
33
+
34
+ profile_procedures(config, procedures, profile)
35
+ clear_coverage(config, profile)
36
+
37
+ read_profile(config, io, profile)
38
+ store_coverage(profile)
39
+
40
+ create_index(config, index, procedures, profile)
41
+ create_reports(config, procedures, profile)
42
+ end
43
+
44
+ # Adds the given procedures to Profile
45
+ #
46
+ def profile_procedures(config, procedures, profile)
47
+ # register each procedure in the Profile
48
+ compiler = Compiler::TraceCompiler.new(config)
49
+ procedures.each do |p|
50
+ result = compiler.compile(p)
51
+ profile.add(p, result[:tags], result)
52
+ end
53
+ end
54
+
55
+ # Clear coverage after procedures have been loaded
56
+ #
57
+ def clear_coverage(config, profile)
58
+ unless config.accumulate?
59
+ puts "clearing previous coverage"
60
+ profile.clear
61
+ end
62
+ end
63
+
64
+ # Reads +io+ for lines matching Profile::PATTERN and records coverage
65
+ #
66
+ def read_profile(config, io, profile)
67
+ np = profile.notice_processor(config)
68
+ io.each{|line| np.call(line) }
69
+ end
70
+
71
+ # Store the coverage Profile on disk
72
+ #
73
+ def store_coverage(profile)
74
+ puts "storing coverage profile"
75
+ profile.store
76
+ end
77
+
78
+ # Create the report's index.html
79
+ #
80
+ def create_index(config, index, procedures, profile)
81
+ puts "creating index"
82
+ reporter = Reporter::Index.new(config, profile)
83
+ reporter.install("resources/piggly.css", "resources/sortable.js", "resources/highlight.js")
84
+ reporter.report(procedures, index)
85
+ end
86
+
87
+ # Create each procedures' HTML report page
88
+ #
89
+ def create_reports(config, procedures, profile)
90
+ puts "creating reports"
91
+ queue = Util::ProcessQueue.new
92
+
93
+ compiler = Compiler::TraceCompiler.new(config)
94
+ reporter = Reporter::Procedure.new(config, profile)
95
+
96
+ Parser.parser
97
+
98
+ procedures.each do |p|
99
+ queue.add do
100
+ unless compiler.stale?(p)
101
+ data = compiler.compile(p)
102
+ path = reporter.report_path(p.source_path(config), ".html")
103
+
104
+ unless profile.empty?(data[:tags])
105
+ changes = ": #{profile.difference(p, data[:tags])}"
106
+ end
107
+
108
+ puts "reporting coverage for #{p.name}#{changes}"
109
+ # pp data[:tags]
110
+ # pp profile[p]
111
+ # puts
112
+
113
+ reporter.report(p)
114
+ end
115
+ end
116
+ end
117
+
118
+ queue.execute
119
+ end
120
+
121
+ def configure(argv, config = Config.new)
122
+ io = $stdin
123
+ p = OptionParser.new do |o|
124
+ o.on("-t", "--dry-run", "only print the names of matching procedures", &o_dry_run(config))
125
+ o.on("-s", "--select PATTERN", "select procedures matching PATTERN", &o_select(config))
126
+ o.on("-r", "--reject PATTERN", "ignore procedures matching PATTERN", &o_reject(config))
127
+ o.on("-c", "--cache-root PATH", "local cache directory", &o_cache_root(config))
128
+ o.on("-o", "--report-root PATH", "report output directory", &o_report_root(config))
129
+ o.on("-a", "--accumulate", "accumulate data from the previous run", &o_accumulate(config))
130
+ o.on("-V", "--version", "show version", &o_version(config))
131
+ o.on("-h", "--help", "show this message") { abort o.to_s }
132
+ o.on("-f", "--input PATH", "read trace messages from PATH") do |path|
133
+ io = if path == "-"
134
+ $stdin
135
+ else
136
+ File.open(path, "rb")
137
+ end
138
+ end
139
+ end
140
+
141
+ begin
142
+ p.parse! argv
143
+
144
+ if io.eql?($stdin) and $stdin.tty?
145
+ raise OptionParser::MissingArgument,
146
+ "stdin must be a pipe, or use --input PATH"
147
+ end
148
+
149
+ return io, config
150
+ rescue OptionParser::InvalidOption,
151
+ OptionParser::InvalidArgument,
152
+ OptionParser::MissingArgument
153
+ puts p
154
+ puts
155
+ puts $!
156
+ exit! 1
157
+ end
158
+ end
159
+ end
160
+
161
+ end
162
+ end
@@ -0,0 +1,90 @@
1
+ module Piggly
2
+ module Command
3
+
4
+ #
5
+ # This command connects to the database, dumps all stored procedures, compiles them
6
+ # with instrumentation code, and finally installs the instrumented code.
7
+ #
8
+ class Trace < Base
9
+ end
10
+
11
+ class << Trace
12
+ def main(argv)
13
+ config = configure(argv)
14
+ connection = connect(config)
15
+ index = Dumper::Index.new(config)
16
+
17
+ dump(connection, index)
18
+
19
+ procedures = filter(config, index)
20
+
21
+ if procedures.empty?
22
+ if config.filters.empty?
23
+ abort "no stored procedures in the cache"
24
+ else
25
+ abort "no stored procedures in the cache matched your criteria"
26
+ end
27
+ elsif config.dry_run?
28
+ puts procedures.map{|p| p.signature }
29
+ exit 0
30
+ end
31
+
32
+ trace(config, procedures)
33
+ install(Installer.new(config, connection), procedures, Profile.new)
34
+ end
35
+
36
+ # Writes all stored procedures in the database to disk
37
+ # @return [void]
38
+ def dump(connection, index)
39
+ index.update(Dumper::ReifiedProcedure.all(connection))
40
+ end
41
+
42
+ # Compiles all the stored procedures on disk and installs them
43
+ # @return [void]
44
+ def trace(config, procedures)
45
+ puts "compiling #{procedures.size} procedures"
46
+
47
+ compiler = Compiler::TraceCompiler.new(config)
48
+ queue = Util::ProcessQueue.new
49
+ procedures.each{|p| queue.add { compiler.compile(p) }}
50
+
51
+ # Force parser to load before we start forking
52
+ Parser.parser
53
+ queue.execute
54
+ end
55
+
56
+ def install(installer, procedures, profile)
57
+ puts "tracing #{procedures.size} procedures"
58
+ installer.install(procedures, profile)
59
+ end
60
+
61
+ # Parses command-line options
62
+ # @return [Config]
63
+ def configure(argv, config = Config.new)
64
+ p = OptionParser.new do |o|
65
+ o.on("-t", "--dry-run", "only print the names of matching procedures", &o_dry_run(config))
66
+ o.on("-s", "--select PATTERN", "select procedures matching PATTERN", &o_select(config))
67
+ o.on("-r", "--reject PATTERN", "ignore procedures matching PATTERN", &o_reject(config))
68
+ o.on("-c", "--cache-root PATH", "local cache directory", &o_cache_root(config))
69
+ o.on("-o", "--report-root PATH", "report output directory", &o_report_root(config))
70
+ o.on("-d", "--database PATH", "read database adapter settings from YAML/JSON file", &o_database_yml(config))
71
+ o.on("-k", "--connection NAME", "use connection adapter NAME", &o_connection_name(config))
72
+ o.on("-V", "--version", "show version", &o_version(config))
73
+ o.on("-h", "--help", "show this message") { abort o.to_s }
74
+ end
75
+
76
+ begin
77
+ p.parse! argv
78
+ config
79
+ rescue OptionParser::InvalidOption,
80
+ OptionParser::InvalidArgument,
81
+ OptionParser::MissingArgument
82
+ puts p
83
+ puts
84
+ puts $!
85
+ exit! 1
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,78 @@
1
+ module Piggly
2
+ module Command
3
+
4
+ class Untrace < Base
5
+ end
6
+
7
+ class << Untrace
8
+ def main(argv)
9
+ config = configure(argv)
10
+ index = Dumper::Index.new(config)
11
+ connection = connect(config)
12
+ procedures = filter(config, index)
13
+
14
+ if procedures.empty?
15
+ if config.filters.empty?
16
+ abort "no stored procedures in the cache"
17
+ else
18
+ abort "no stored procedures in the cache matched your criteria"
19
+ end
20
+ elsif config.dry_run?
21
+ puts procedures.map{|x| x.signature }
22
+ exit 0
23
+ end
24
+
25
+ untrace(Installer.new(config, connection), procedures)
26
+ end
27
+
28
+ #
29
+ # Restores database procedures from file cache
30
+ #
31
+ def untrace(installer, procedures)
32
+ puts "restoring #{procedures.size} procedures"
33
+ installer.uninstall(procedures)
34
+ end
35
+
36
+ #
37
+ # Returns a list of Procedure values that satisfy at least one of the given filters
38
+ #
39
+ def find_procedures(filters, index)
40
+ if filters.empty?
41
+ index.procedures
42
+ else
43
+ filters.inject(Set.new){|set, filter| set | index.procedures.select(&filter) }
44
+ end
45
+ end
46
+
47
+ #
48
+ # Parses command-line options
49
+ #
50
+ def configure(argv, config = Config.new)
51
+ p = OptionParser.new do |o|
52
+ o.on("-t", "--dry-run", "only print the names of matching procedures", &o_dry_run(config))
53
+ o.on("-s", "--select PATTERN", "select procedures matching PATTERN", &o_select(config))
54
+ o.on("-r", "--reject PATTERN", "ignore procedures matching PATTERN", &o_reject(config))
55
+ o.on("-c", "--cache-root PATH", "local cache directory", &o_cache_root(config))
56
+ o.on("-d", "--database PATH", "read 'piggly' database adapter settings from YAML file", &o_database_yml(config))
57
+ o.on("-k", "--connection NAME", "use connection adapter NAME", &o_connection_name(config))
58
+ o.on("-V", "--version", "show version", &o_version(config))
59
+ o.on("-h", "--help", "show this message") { abort o.to_s }
60
+ end
61
+
62
+ begin
63
+ p.parse! argv
64
+ config
65
+ rescue OptionParser::InvalidOption,
66
+ OptionParser::InvalidArgument,
67
+ OptionParser::MissingArgument
68
+ puts p
69
+ puts
70
+ puts $!
71
+ exit! 1
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+ end