pgdexter 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 78c0747c456e498798accd4e4881a8041d246e07
4
- data.tar.gz: c4c9cffe3e04d07675c36abf0532db3634e356ea
3
+ metadata.gz: 714767248afe28ad9e354ebb6485b34a8d6aadd0
4
+ data.tar.gz: 545eaaea1df0312049f4cecff3fe9b03b80a6cd9
5
5
  SHA512:
6
- metadata.gz: df826f95f34809bd94cb7450cc43d4bd6beed81606e6e7079842dd4a05e983d4041bf956645c09a290cf8fa37140917da1a12fe332d6268969da5310f688e148
7
- data.tar.gz: f7015b408e7864bfe7e4e136cbb60acbe837cbbe640ec4f43dfb2d9631f2d4cb2b4c86826cb58051d68941bdfa6fd6625029babe7c1609e084db1a4f92a23652
6
+ metadata.gz: '096684887c5d5a48a2df74a2dae6cf70bc2b5e98dd5dc6d7d6d8b583bfb76193ce86166972d9553f89452f16629a8576a30f2627fd8d0b16a9672ce76a316835'
7
+ data.tar.gz: d128734845414672a3f470f138c6a422dc2b24024a66866e50524fe3bb3e949bd4a899305a93f71fb14af3b56820f66e20a00d5ba5788cd0c88bd0afaa064e32
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.3.2
2
+
3
+ - Fixed parsing issue with named prepared statements
4
+ - Fixed parsing issue with multiline queries in csv format
5
+ - Better explanations for indexing decisions
6
+
1
7
  ## 0.3.1
2
8
 
3
9
  - Added support for queries with bind variables
data/exe/dexter CHANGED
@@ -3,6 +3,8 @@
3
3
  require "dexter"
4
4
  begin
5
5
  Dexter::Client.new(ARGV).perform
6
+ rescue Dexter::Abort => e
7
+ abort e.message
6
8
  rescue Interrupt => e
7
9
  # do nothing
8
10
  end
data/lib/dexter/client.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  module Dexter
2
2
  class Client
3
+ include Logging
4
+
3
5
  attr_reader :arguments, :options
4
6
 
5
7
  def initialize(args)
@@ -14,6 +16,7 @@ module Dexter
14
16
  query = Query.new(options[:statement])
15
17
  Indexer.new(options).process_queries([query])
16
18
  elsif options[:pg_stat_statements]
19
+ # TODO support streaming option
17
20
  Indexer.new(options).process_stat_statements
18
21
  elsif arguments.any?
19
22
  ARGV.replace(arguments)
@@ -35,12 +38,12 @@ Options:)
35
38
  o.string "--include", "only include specific tables"
36
39
  o.string "--input-format", "input format", default: "stderr"
37
40
  o.integer "--interval", "time to wait between processing queries, in seconds", default: 60
38
- o.float "--min-calls", "only process queries that have been called a certain number of times", default: 0
39
- o.float "--min-time", "only process queries that have consumed a certain amount of DB time, in minutes", default: 0
40
- o.boolean "--pg-stat-statements", "use pg_stat_statements", default: false, help: false
41
41
  o.boolean "--log-explain", "log explain", default: false, help: false
42
42
  o.string "--log-level", "log level", default: "info"
43
43
  o.boolean "--log-sql", "log sql", default: false
44
+ o.float "--min-calls", "only process queries that have been called a certain number of times", default: 0
45
+ o.float "--min-time", "only process queries that have consumed a certain amount of DB time, in minutes", default: 0
46
+ o.boolean "--pg-stat-statements", "use pg_stat_statements", default: false, help: false
44
47
  o.string "-s", "--statement", "process a single statement"
45
48
  # separator must go here to show up correctly - slop bug?
46
49
  o.separator ""
@@ -72,9 +75,5 @@ Options:)
72
75
  rescue Slop::Error => e
73
76
  abort e.message
74
77
  end
75
-
76
- def log(message)
77
- $stderr.puts message
78
- end
79
78
  end
80
79
  end
@@ -2,10 +2,15 @@ require "csv"
2
2
 
3
3
  module Dexter
4
4
  class CsvLogParser < LogParser
5
+ FIRST_LINE_REGEX = /\A.+/
6
+
5
7
  def perform
6
- CSV.new(@logfile).each do |row|
8
+ CSV.new(@logfile.to_io).each do |row|
7
9
  if (m = REGEX.match(row[13]))
8
- active_line = m[3]
10
+ # replace first line with match
11
+ # needed for multiline queries
12
+ active_line = row[13].sub(FIRST_LINE_REGEX, m[3])
13
+
9
14
  add_parameters(active_line, row[14]) if row[14]
10
15
  process_entry(active_line, m[1].to_f)
11
16
  end
@@ -30,21 +30,11 @@ module Dexter
30
30
 
31
31
  tables = Set.new(database_tables)
32
32
 
33
- if @include_tables
34
- include_set = Set.new(@include_tables)
35
- tables.keep_if { |t| include_set.include?(t) || include_set.include?(t.split(".")[-1]) }
36
- end
37
-
38
- if @exclude_tables.any?
39
- exclude_set = Set.new(@exclude_tables)
40
- tables.delete_if { |t| exclude_set.include?(t) || exclude_set.include?(t.split(".")[-1]) }
41
- end
42
-
43
33
  # map tables without schema to schema
44
34
  no_schema_tables = {}
45
35
  search_path_index = Hash[search_path.map.with_index.to_a]
46
36
  tables.group_by { |t| t.split(".")[-1] }.each do |group, t2|
47
- no_schema_tables[group] = t2.sort_by { |t| search_path_index[t.split(".")[0]] || 1000000 }[0]
37
+ no_schema_tables[group] = t2.sort_by { |t| [search_path_index[t.split(".")[0]] || 1000000, t] }[0]
48
38
  end
49
39
 
50
40
  # filter queries from other databases and system tables
@@ -59,11 +49,29 @@ module Dexter
59
49
  # set tables
60
50
  tables = Set.new(queries.reject(&:missing_tables).flat_map(&:tables))
61
51
 
52
+ # must come after missing tables set
53
+ if @include_tables
54
+ include_set = Set.new(@include_tables)
55
+ tables.keep_if { |t| include_set.include?(t) || include_set.include?(t.split(".")[-1]) }
56
+ end
57
+
58
+ if @exclude_tables.any?
59
+ exclude_set = Set.new(@exclude_tables)
60
+ tables.delete_if { |t| exclude_set.include?(t) || exclude_set.include?(t.split(".")[-1]) }
61
+ end
62
+
63
+ # remove system tables
64
+ tables.delete_if { |t| t.start_with?("information_schema.") || t.start_with?("pg_catalog.") }
65
+
66
+ queries.each do |query|
67
+ query.candidate_tables = !query.missing_tables && query.tables.any? { |t| tables.include?(t) }
68
+ end
69
+
62
70
  # analyze tables if needed
63
71
  analyze_tables(tables) if tables.any? && (@analyze || @log_level == "debug2")
64
72
 
65
73
  # create hypothetical indexes and explain queries
66
- candidates = tables.any? ? create_hypothetical_indexes(queries.reject(&:missing_tables), tables) : {}
74
+ candidates = tables.any? ? create_hypothetical_indexes(queries.select(&:candidate_tables), tables) : {}
67
75
 
68
76
  # see if new indexes were used and meet bar
69
77
  new_indexes = determine_indexes(queries, candidates, tables)
@@ -344,7 +352,14 @@ module Dexter
344
352
  log "-" * 80
345
353
  log "Query #{query.fingerprint}"
346
354
  log "Total time: #{(query.total_time / 60000.0).round(1)} min, avg time: #{(query.total_time / query.calls.to_f).round} ms, calls: #{query.calls}" if query.total_time
347
- if tables.empty?
355
+
356
+ if query.fingerprint == "unknown"
357
+ log "Could not parse query"
358
+ elsif query.tables.empty?
359
+ log "No tables"
360
+ elsif query.missing_tables
361
+ log "Tables not present in current database"
362
+ elsif !query.candidate_tables
348
363
  log "No candidate tables for indexes"
349
364
  elsif query.explainable? && !query.high_cost?
350
365
  log "Low initial cost: #{query.initial_cost}"
@@ -354,15 +369,9 @@ module Dexter
354
369
  log "Pass1: #{query.costs[1]} : #{log_indexes(query.pass1_indexes || [])}"
355
370
  log "Pass2: #{query.costs[2]} : #{log_indexes(query.pass2_indexes || [])}"
356
371
  log "Final: #{query.new_cost} : #{log_indexes(query.suggest_index ? query_indexes : [])}"
357
- if query_indexes.any? && !query.suggest_index
372
+ if query_indexes.size == 1 && !query.suggest_index
358
373
  log "Need 50% cost savings to suggest index"
359
374
  end
360
- elsif query.fingerprint == "unknown"
361
- log "Could not parse query"
362
- elsif query.tables.empty?
363
- log "No tables"
364
- elsif query.missing_tables
365
- log "Tables not present in current database"
366
375
  else
367
376
  log "Could not run explain"
368
377
  end
@@ -452,9 +461,7 @@ module Dexter
452
461
  FROM
453
462
  information_schema.tables
454
463
  WHERE
455
- table_catalog = current_database() AND
456
- table_schema NOT IN ('pg_catalog', 'information_schema')
457
- AND table_type = 'BASE TABLE'
464
+ table_catalog = current_database()
458
465
  SQL
459
466
  result.map { |r| r["table_name"] }
460
467
  end
@@ -549,7 +556,7 @@ module Dexter
549
556
  end
550
557
 
551
558
  def search_path
552
- execute("SHOW search_path")[0]["search_path"].split(",").map(&:strip)
559
+ execute("SELECT current_schemas(true)")[0]["current_schemas"][1..-2].split(",")
553
560
  end
554
561
 
555
562
  def unquote(part)
@@ -1,6 +1,8 @@
1
1
  module Dexter
2
2
  class LogParser
3
- REGEX = /duration: (\d+\.\d+) ms (statement|execute <unnamed>): (.+)/
3
+ include Logging
4
+
5
+ REGEX = /duration: (\d+\.\d+) ms (statement|execute [^:]+): (.+)/
4
6
  LINE_SEPERATOR = ": ".freeze
5
7
  DETAIL_LINE = "DETAIL: ".freeze
6
8
 
@@ -3,5 +3,9 @@ module Dexter
3
3
  def log(message = "")
4
4
  puts message unless $log_level == "error"
5
5
  end
6
+
7
+ def abort(message)
8
+ raise Dexter::Abort, message
9
+ end
6
10
  end
7
11
  end
data/lib/dexter/query.rb CHANGED
@@ -2,7 +2,7 @@ module Dexter
2
2
  class Query
3
3
  attr_reader :statement, :fingerprint, :plans
4
4
  attr_writer :tables
5
- attr_accessor :missing_tables, :new_cost, :total_time, :calls, :indexes, :suggest_index, :pass1_indexes, :pass2_indexes
5
+ attr_accessor :missing_tables, :new_cost, :total_time, :calls, :indexes, :suggest_index, :pass1_indexes, :pass2_indexes, :candidate_tables
6
6
 
7
7
  def initialize(statement, fingerprint = nil)
8
8
  @statement = statement
@@ -1,3 +1,3 @@
1
1
  module Dexter
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.2"
3
3
  end
data/lib/dexter.rb CHANGED
@@ -13,3 +13,7 @@ require "dexter/log_parser"
13
13
  require "dexter/csv_log_parser"
14
14
  require "dexter/processor"
15
15
  require "dexter/query"
16
+
17
+ module Dexter
18
+ class Abort < StandardError; end
19
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgdexter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-12-29 00:00:00.000000000 Z
11
+ date: 2018-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: slop