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 +4 -4
- data/CHANGELOG.md +6 -0
- data/exe/dexter +2 -0
- data/lib/dexter/client.rb +6 -7
- data/lib/dexter/csv_log_parser.rb +7 -2
- data/lib/dexter/indexer.rb +31 -24
- data/lib/dexter/log_parser.rb +3 -1
- data/lib/dexter/logging.rb +4 -0
- data/lib/dexter/query.rb +1 -1
- data/lib/dexter/version.rb +1 -1
- data/lib/dexter.rb +4 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 714767248afe28ad9e354ebb6485b34a8d6aadd0
|
4
|
+
data.tar.gz: 545eaaea1df0312049f4cecff3fe9b03b80a6cd9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '096684887c5d5a48a2df74a2dae6cf70bc2b5e98dd5dc6d7d6d8b583bfb76193ce86166972d9553f89452f16629a8576a30f2627fd8d0b16a9672ce76a316835'
|
7
|
+
data.tar.gz: d128734845414672a3f470f138c6a422dc2b24024a66866e50524fe3bb3e949bd4a899305a93f71fb14af3b56820f66e20a00d5ba5788cd0c88bd0afaa064e32
|
data/CHANGELOG.md
CHANGED
data/exe/dexter
CHANGED
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
|
-
|
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
|
data/lib/dexter/indexer.rb
CHANGED
@@ -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.
|
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
|
-
|
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.
|
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()
|
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("
|
559
|
+
execute("SELECT current_schemas(true)")[0]["current_schemas"][1..-2].split(",")
|
553
560
|
end
|
554
561
|
|
555
562
|
def unquote(part)
|
data/lib/dexter/log_parser.rb
CHANGED
data/lib/dexter/logging.rb
CHANGED
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
|
data/lib/dexter/version.rb
CHANGED
data/lib/dexter.rb
CHANGED
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.
|
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:
|
11
|
+
date: 2018-01-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: slop
|