pgdexter 0.3.3 → 0.3.8

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
- SHA1:
3
- metadata.gz: 14bc122b136301535793c29b19336a2d0614f640
4
- data.tar.gz: 624c8c6ae5aabd8a5e2150887aa83efe6e67f9b4
2
+ SHA256:
3
+ metadata.gz: 3940bf892014f2337cc5cd95c85b66f245b3725ad7ce84397c4eb4f9e3751c3c
4
+ data.tar.gz: b8fc834cda0161c8ef0b44853c14f5f608c00f373e6a95481321e45611d2d5b4
5
5
  SHA512:
6
- metadata.gz: 44224c687d8590c0441d587b432fde3b85cef77728980059d497ade992cd953e8f8445747de61c984d559f55d74461ac284c6c8423c0f1f8b08e439e361c141d
7
- data.tar.gz: 42ec40ec7527dfc3093853ec013c5b30bacbaee5cbe8d27a1530649bfbdc16c639d9d93d65e72b062d53b33bff1d0a1bec67e971d283f5d5a2e6fea7d884fccd
6
+ metadata.gz: 463430e69b2a1f08ca411db0a4938c7243839e78f81db72455d508ff1e9de1a752889bff8a3e42486cf4859042065b0847f0b6c0216c80c9b9a36a10cf360753
7
+ data.tar.gz: d31949fc719e863aa56672fd807141dfbeb521d2ce2525ca551109e09840115c3091e7130cc47617a6b836ff31853dbf12a1bb9fb4dea492b17cbf7c9bfd5c8e
@@ -1,22 +1,47 @@
1
- ## 0.3.3
1
+ ## 0.3.8 (2020-08-17)
2
+
3
+ - Colorize output
4
+ - Fixed error when unable to parse view definitions
5
+
6
+ ## 0.3.7 (2020-07-10)
7
+
8
+ - Fixed help output
9
+
10
+ ## 0.3.6 (2020-03-30)
11
+
12
+ - Fixed warning with Ruby 2.7
13
+
14
+ ## 0.3.5 (2018-04-30)
15
+
16
+ - Added `sql` input format
17
+ - Fixed error for queries with double dash comments
18
+ - Fixed connection threading issue with `--pg-stat-activity` option
19
+
20
+ ## 0.3.4 (2018-04-09)
21
+
22
+ - Fixed `--username` option
23
+ - Fixed `JSON::NestingError`
24
+ - Added `--pg-stat-activity` option
25
+
26
+ ## 0.3.3 (2018-02-22)
2
27
 
3
28
  - Added support for views and materialized views
4
29
  - Better handle case when multiple indexes are found for a query
5
30
  - Added `--min-cost-savings-pct` option
6
31
 
7
- ## 0.3.2
32
+ ## 0.3.2 (2018-01-04)
8
33
 
9
34
  - Fixed parsing issue with named prepared statements
10
35
  - Fixed parsing issue with multiline queries in csv format
11
36
  - Better explanations for indexing decisions
12
37
 
13
- ## 0.3.1
38
+ ## 0.3.1 (2017-12-28)
14
39
 
15
40
  - Added support for queries with bind variables
16
41
  - Fixed error with streaming logs as csv format
17
42
  - Handle malformed CSV gracefully
18
43
 
19
- ## 0.3.0
44
+ ## 0.3.0 (2017-12-22)
20
45
 
21
46
  - Added support for schemas
22
47
  - Added support for csv format
@@ -24,12 +49,12 @@
24
49
  - Added `--min-calls` option
25
50
  - Fixed debug output when indexes not found
26
51
 
27
- ## 0.2.1
52
+ ## 0.2.1 (2017-09-02)
28
53
 
29
54
  - Fixed bad suggestions
30
55
  - Improved debugging output
31
56
 
32
- ## 0.2.0
57
+ ## 0.2.0 (2017-08-27)
33
58
 
34
59
  - Added same connection options as `psql`
35
60
  - Added support for multiple files
@@ -40,38 +65,38 @@ Breaking
40
65
 
41
66
  - `-h` option changed to `--host` instead of `--help` for consistency with `psql`
42
67
 
43
- ## 0.1.6
68
+ ## 0.1.6 (2017-08-26)
44
69
 
45
70
  - Significant performance improvements
46
71
  - Added `--include` option
47
72
 
48
- ## 0.1.5
73
+ ## 0.1.5 (2017-08-14)
49
74
 
50
75
  - Added support for non-`SELECT` queries
51
76
  - Added `--pg-stat-statements` option
52
77
  - Added advisory locks
53
78
  - Added support for running as a non-superuser
54
79
 
55
- ## 0.1.4
80
+ ## 0.1.4 (2017-07-02)
56
81
 
57
82
  - Added support for multicolumn indexes
58
83
 
59
- ## 0.1.3
84
+ ## 0.1.3 (2017-06-30)
60
85
 
61
86
  - Fixed error with non-lowercase columns
62
87
  - Fixed error with `json` columns
63
88
 
64
- ## 0.1.2
89
+ ## 0.1.2 (2017-06-26)
65
90
 
66
91
  - Added `--exclude` option
67
92
  - Added `--log-sql` option
68
93
 
69
- ## 0.1.1
94
+ ## 0.1.1 (2017-06-25)
70
95
 
71
96
  - Added `--interval` option
72
97
  - Added `--min-time` option
73
98
  - Added `--log-level` option
74
99
 
75
- ## 0.1.0
100
+ ## 0.1.0 (2017-06-24)
76
101
 
77
102
  - Launched
@@ -1,4 +1,4 @@
1
- Copyright (c) 2017 Andrew Kane
1
+ Copyright (c) 2017-2020 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -2,18 +2,18 @@
2
2
 
3
3
  The automatic indexer for Postgres
4
4
 
5
- [Read about how it works](https://medium.com/@ankane/introducing-dexter-the-automatic-indexer-for-postgres-5f8fa8b28f27)
5
+ [Read about how it works](https://ankane.org/introducing-dexter)
6
6
 
7
7
  [![Build Status](https://travis-ci.org/ankane/dexter.svg?branch=master)](https://travis-ci.org/ankane/dexter)
8
8
 
9
9
  ## Installation
10
10
 
11
- First, install [HypoPG](https://github.com/dalibo/hypopg) on your database server. This doesn’t require a restart.
11
+ First, install [HypoPG](https://github.com/HypoPG/hypopg) on your database server. This doesn’t require a restart.
12
12
 
13
13
  ```sh
14
14
  cd /tmp
15
- curl -L https://github.com/dalibo/hypopg/archive/1.1.0.tar.gz | tar xz
16
- cd hypopg-1.1.0
15
+ curl -L https://github.com/HypoPG/hypopg/archive/1.1.4.tar.gz | tar xz
16
+ cd hypopg-1.1.4
17
17
  make
18
18
  make install # may need sudo
19
19
  ```
@@ -104,13 +104,19 @@ or pass files:
104
104
  dexter <connection-options> <file1> <file2>
105
105
  ```
106
106
 
107
+ or collect running queries with:
108
+
109
+ ```sh
110
+ dexter <connection-options> --pg-stat-activity
111
+ ```
112
+
107
113
  or use the [pg_stat_statements](https://www.postgresql.org/docs/current/static/pgstatstatements.html) extension:
108
114
 
109
115
  ```sh
110
116
  dexter <connection-options> --pg-stat-statements
111
117
  ```
112
118
 
113
- > Note: Logs are highly preferred over pg_stat_statements, as pg_stat_statements often doesn’t store enough information to optimize queries.
119
+ > Note: Logs or running queries are highly preferred over pg_stat_statements, as pg_stat_statements often doesn’t store enough information to optimize queries.
114
120
 
115
121
  ### Collection Options
116
122
 
@@ -140,10 +146,10 @@ dexter --interval 60 # seconds
140
146
 
141
147
  ## Examples
142
148
 
143
- Ubuntu with PostgreSQL 9.6
149
+ Ubuntu with PostgreSQL 12
144
150
 
145
151
  ```sh
146
- tail -F -n +1 /var/log/postgresql/postgresql-9.6-main.log | sudo -u postgres dexter dbname
152
+ tail -F -n +1 /var/log/postgresql/postgresql-12-main.log | sudo -u postgres dexter dbname
147
153
  ```
148
154
 
149
155
  Homebrew on Mac
@@ -209,6 +215,10 @@ gem specific_install https://github.com/ankane/dexter.git
209
215
 
210
216
  This software wouldn’t be possible without [HypoPG](https://github.com/dalibo/hypopg), which allows you to create hypothetical indexes, and [pg_query](https://github.com/lfittl/pg_query), which allows you to parse and fingerprint queries. A big thanks to Dalibo and Lukas Fittl respectively.
211
217
 
218
+ ## Research
219
+
220
+ This is known as the Index Selection Problem (ISP).
221
+
212
222
  ## Contributing
213
223
 
214
224
  Everyone is encouraged to help improve this project. Here are a few ways you can help:
@@ -218,17 +228,18 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
218
228
  - Write, clarify, or fix documentation
219
229
  - Suggest or add new features
220
230
 
221
- To get started, run:
231
+ To get started with development, run:
222
232
 
223
233
  ```sh
224
234
  git clone https://github.com/ankane/dexter.git
225
235
  cd dexter
226
- bundle
227
- rake install
236
+ bundle install
237
+ bundle exec rake install
228
238
  ```
229
239
 
230
240
  To run tests, use:
231
241
 
232
242
  ```sh
233
- rake test
243
+ createdb dexter_test
244
+ bundle exec rake test
234
245
  ```
data/exe/dexter CHANGED
@@ -1,10 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # handle interrupts
4
+ trap("SIGINT") { abort }
5
+
3
6
  require "dexter"
4
- begin
5
- Dexter::Client.new(ARGV).perform
6
- rescue Dexter::Abort => e
7
- abort e.message
8
- rescue Interrupt => e
9
- # do nothing
10
- end
7
+ Dexter::Client.start
@@ -1,16 +1,22 @@
1
- require "dexter/version"
2
- require "slop"
1
+ # dependencies
3
2
  require "pg"
4
3
  require "pg_query"
5
- require "time"
4
+ require "slop"
5
+
6
+ # stdlib
6
7
  require "set"
7
- require "thread"
8
+ require "time"
9
+
10
+ # modules
11
+ require "dexter/version"
8
12
  require "dexter/logging"
9
13
  require "dexter/client"
10
14
  require "dexter/collector"
11
15
  require "dexter/indexer"
12
16
  require "dexter/log_parser"
13
17
  require "dexter/csv_log_parser"
18
+ require "dexter/pg_stat_activity_parser"
19
+ require "dexter/sql_log_parser"
14
20
  require "dexter/processor"
15
21
  require "dexter/query"
16
22
 
@@ -1,9 +1,16 @@
1
1
  module Dexter
2
2
  class Client
3
+ extend Logging
3
4
  include Logging
4
5
 
5
6
  attr_reader :arguments, :options
6
7
 
8
+ def self.start
9
+ Dexter::Client.new(ARGV).perform
10
+ rescue Dexter::Abort, PG::UndefinedFile => e
11
+ abort colorize(e.message.strip, :red)
12
+ end
13
+
7
14
  def initialize(args)
8
15
  @arguments, @options = parse_args(args)
9
16
  end
@@ -18,6 +25,8 @@ module Dexter
18
25
  elsif options[:pg_stat_statements]
19
26
  # TODO support streaming option
20
27
  Indexer.new(options).process_stat_statements
28
+ elsif options[:pg_stat_activity]
29
+ Processor.new(:pg_stat_activity, options).perform
21
30
  elsif arguments.any?
22
31
  ARGV.replace(arguments)
23
32
  Processor.new(ARGF, options).perform
@@ -29,9 +38,9 @@ module Dexter
29
38
  def parse_args(args)
30
39
  opts = Slop.parse(args) do |o|
31
40
  o.banner = %(Usage:
32
- dexter [options]
33
-
34
- Options:)
41
+ dexter [options])
42
+ o.separator ""
43
+ o.separator "Options:"
35
44
  o.boolean "--analyze", "analyze tables that haven't been analyzed in the past hour", default: false
36
45
  o.boolean "--create", "create indexes", default: false
37
46
  o.array "--exclude", "prevent specific tables from being indexed"
@@ -44,11 +53,9 @@ Options:)
44
53
  o.float "--min-calls", "only process queries that have been called a certain number of times", default: 0
45
54
  o.float "--min-time", "only process queries that have consumed a certain amount of DB time, in minutes", default: 0
46
55
  o.integer "--min-cost-savings-pct", default: 50, help: false
56
+ o.boolean "--pg-stat-activity", "use pg_stat_activity", default: false, help: false
47
57
  o.boolean "--pg-stat-statements", "use pg_stat_statements", default: false, help: false
48
58
  o.string "-s", "--statement", "process a single statement"
49
- # separator must go here to show up correctly - slop bug?
50
- o.separator ""
51
- o.separator "Connection options:"
52
59
  o.on "-v", "--version", "print the version" do
53
60
  log Dexter::VERSION
54
61
  exit
@@ -57,10 +64,12 @@ Options:)
57
64
  log o
58
65
  exit
59
66
  end
60
- o.string "-U", "--username"
61
- o.string "-d", "--dbname"
62
- o.string "-h", "--host"
63
- o.integer "-p", "--port"
67
+ o.separator ""
68
+ o.separator "Connection options:"
69
+ o.string "-d", "--dbname", "database name"
70
+ o.string "-h", "--host", "database host"
71
+ o.integer "-p", "--port", "database port"
72
+ o.string "-U", "--username", "database user"
64
73
  end
65
74
 
66
75
  arguments = opts.arguments
@@ -70,11 +79,11 @@ Options:)
70
79
 
71
80
  # TODO don't use global var
72
81
  $log_level = options[:log_level].to_s.downcase
73
- abort "Unknown log level" unless ["error", "info", "debug", "debug2", "debug3"].include?($log_level)
82
+ raise Dexter::Abort, "Unknown log level" unless ["error", "info", "debug", "debug2", "debug3"].include?($log_level)
74
83
 
75
84
  [arguments, options]
76
85
  rescue Slop::Error => e
77
- abort e.message
86
+ raise Dexter::Abort, e.message
78
87
  end
79
88
  end
80
89
  end
@@ -16,7 +16,7 @@ module Dexter
16
16
  end
17
17
  end
18
18
  rescue CSV::MalformedCSVError => e
19
- abort "ERROR: #{e.message}"
19
+ raise Dexter::Abort, "ERROR: #{e.message}"
20
20
  end
21
21
  end
22
22
  end
@@ -14,6 +14,7 @@ module Dexter
14
14
  @analyze = options[:analyze]
15
15
  @min_cost_savings_pct = options[:min_cost_savings_pct].to_i
16
16
  @options = options
17
+ @mutex = Mutex.new
17
18
 
18
19
  create_extension unless extension_exists?
19
20
  execute("SET lock_timeout = '5s'")
@@ -25,6 +26,23 @@ module Dexter
25
26
  process_queries(queries)
26
27
  end
27
28
 
29
+ def stat_activity
30
+ execute <<-SQL
31
+ SELECT
32
+ pid || ':' || COALESCE(query_start, xact_start) AS id,
33
+ query,
34
+ EXTRACT(EPOCH FROM NOW() - COALESCE(query_start, xact_start)) * 1000.0 AS duration_ms
35
+ FROM
36
+ pg_stat_activity
37
+ WHERE
38
+ datname = current_database()
39
+ AND state = 'active'
40
+ AND pid != pg_backend_pid()
41
+ ORDER BY
42
+ 1
43
+ SQL
44
+ end
45
+
28
46
  def process_queries(queries)
29
47
  # reset hypothetical indexes
30
48
  reset_hypothetical_indexes
@@ -89,13 +107,13 @@ module Dexter
89
107
  analyze_tables(tables) if tables.any? && (@analyze || @log_level == "debug2")
90
108
 
91
109
  # create hypothetical indexes and explain queries
92
- candidates = tables.any? ? create_hypothetical_indexes(queries.select(&:candidate_tables), tables) : {}
110
+ candidates = tables.any? ? create_hypothetical_indexes(queries.select(&:candidate_tables)) : {}
93
111
 
94
112
  # see if new indexes were used and meet bar
95
113
  new_indexes = determine_indexes(queries, candidates, tables)
96
114
 
97
115
  # display and create new indexes
98
- show_and_create_indexes(new_indexes, queries, tables)
116
+ show_and_create_indexes(new_indexes, queries)
99
117
  end
100
118
 
101
119
  private
@@ -105,9 +123,9 @@ module Dexter
105
123
  begin
106
124
  execute("CREATE EXTENSION IF NOT EXISTS hypopg")
107
125
  rescue PG::UndefinedFile
108
- abort "Install HypoPG first: https://github.com/ankane/dexter#installation"
126
+ raise Dexter::Abort, "Install HypoPG first: https://github.com/ankane/dexter#installation"
109
127
  rescue PG::InsufficientPrivilege
110
- abort "Use a superuser to run: CREATE EXTENSION hypopg"
128
+ raise Dexter::Abort, "Use a superuser to run: CREATE EXTENSION hypopg"
111
129
  end
112
130
  end
113
131
 
@@ -164,9 +182,9 @@ module Dexter
164
182
  query.plans << plan(query.statement)
165
183
  if @log_explain
166
184
  # Pass format to prevent ANALYZE
167
- puts execute("EXPLAIN (FORMAT TEXT) #{safe_statement(query.statement)}").map { |r| r["QUERY PLAN"] }.join("\n")
185
+ puts execute("EXPLAIN (FORMAT TEXT) #{safe_statement(query.statement)}", pretty: false).map { |r| r["QUERY PLAN"] }.join("\n")
168
186
  end
169
- rescue PG::Error => e
187
+ rescue PG::Error, JSON::NestingError => e
170
188
  if @log_explain
171
189
  log e.message
172
190
  end
@@ -175,7 +193,7 @@ module Dexter
175
193
  end
176
194
  end
177
195
 
178
- def create_hypothetical_indexes(queries, tables)
196
+ def create_hypothetical_indexes(queries)
179
197
  candidates = {}
180
198
 
181
199
  # get initial costs for queries
@@ -345,11 +363,12 @@ module Dexter
345
363
  winning_cost < query.initial_cost * savings_ratio
346
364
  end
347
365
 
366
+ query_indexes = [winning_index]
367
+ new_cost3 = winning_cost
368
+ query.pass3_indexes = query_indexes
369
+
348
370
  if use_winning
349
- query_indexes = [winning_index]
350
371
  cost_savings3 = true
351
- new_cost3 = winning_cost
352
- query.pass3_indexes = query_indexes
353
372
  else
354
373
  suggest_index = false
355
374
  end
@@ -398,11 +417,11 @@ module Dexter
398
417
  end
399
418
  end
400
419
 
401
- def show_and_create_indexes(new_indexes, queries, tables)
420
+ def show_and_create_indexes(new_indexes, queries)
402
421
  # print summary
403
422
  if new_indexes.any?
404
423
  new_indexes.each do |index|
405
- log "Index found: #{index[:table]} (#{index[:columns].join(", ")})"
424
+ log colorize("Index found: #{index[:table]} (#{index[:columns].join(", ")})", :green)
406
425
  end
407
426
  else
408
427
  log "No new indexes found"
@@ -439,7 +458,7 @@ module Dexter
439
458
  log "Pass3: #{query.costs[3]} : #{log_indexes(query.pass3_indexes || [])}"
440
459
  end
441
460
  log "Final: #{query.new_cost} : #{log_indexes(query.suggest_index ? query_indexes : [])}"
442
- if query_indexes.size == 1 && !query.suggest_index
461
+ if (query.pass1_indexes.any? || query.pass2_indexes.any?) && !query.suggest_index
443
462
  log "Need #{@min_cost_savings_pct}% cost savings to suggest index"
444
463
  end
445
464
  else
@@ -479,6 +498,9 @@ module Dexter
479
498
 
480
499
  def conn
481
500
  @conn ||= begin
501
+ # set connect timeout if none set
502
+ ENV["PGCONNECT_TIMEOUT"] ||= "2"
503
+
482
504
  if @options[:dbname] =~ /\Apostgres(ql)?:\/\//
483
505
  config = @options[:dbname]
484
506
  else
@@ -486,17 +508,17 @@ module Dexter
486
508
  host: @options[:host],
487
509
  port: @options[:port],
488
510
  dbname: @options[:dbname],
489
- user: @options[:user]
511
+ user: @options[:username]
490
512
  }.reject { |_, value| value.to_s.empty? }
491
513
  config = config[:dbname] if config.keys == [:dbname] && config[:dbname].include?("=")
492
514
  end
493
515
  PG::Connection.new(config)
494
516
  end
495
517
  rescue PG::ConnectionBad => e
496
- abort e.message
518
+ raise Dexter::Abort, e.message
497
519
  end
498
520
 
499
- def execute(query)
521
+ def execute(query, pretty: true)
500
522
  # use exec_params instead of exec for security
501
523
  #
502
524
  # Unlike PQexec, PQexecParams allows at most one SQL command in the given string.
@@ -504,14 +526,17 @@ module Dexter
504
526
  # This is a limitation of the underlying protocol, but has some usefulness
505
527
  # as an extra defense against SQL-injection attacks.
506
528
  # https://www.postgresql.org/docs/current/static/libpq-exec.html
507
- query = squish(query)
508
- log "SQL: #{query}" if @log_sql
509
- conn.exec_params(query, []).to_a
529
+ query = squish(query) if pretty
530
+ log colorize("[sql] #{query}", :cyan) if @log_sql
531
+
532
+ @mutex.synchronize do
533
+ conn.exec_params(query, []).to_a
534
+ end
510
535
  end
511
536
 
512
537
  def plan(query)
513
538
  # strip semi-colons as another measure of defense
514
- JSON.parse(execute("EXPLAIN (FORMAT JSON) #{safe_statement(query)}").first["QUERY PLAN"]).first["Plan"]
539
+ JSON.parse(execute("EXPLAIN (FORMAT JSON) #{safe_statement(query)}", pretty: false).first["QUERY PLAN"], max_nesting: 1000).first["Plan"]
515
540
  end
516
541
 
517
542
  # TODO for multicolumn indexes, use ordering
@@ -571,7 +596,13 @@ module Dexter
571
596
 
572
597
  view_tables = {}
573
598
  result.each do |row|
574
- view_tables[row["table_name"]] = PgQuery.parse(row["definition"]).tables
599
+ begin
600
+ view_tables[row["table_name"]] = PgQuery.parse(row["definition"]).tables
601
+ rescue PgQuery::ParseError
602
+ if @log_level.start_with?("debug")
603
+ log colorize("ERROR: Cannot parse view definition: #{row["table_name"]}", :red)
604
+ end
605
+ end
575
606
  end
576
607
 
577
608
  view_tables
@@ -1,11 +1,26 @@
1
1
  module Dexter
2
2
  module Logging
3
+ COLOR_CODES = {
4
+ red: 31,
5
+ green: 32,
6
+ yellow: 33,
7
+ cyan: 36
8
+ }
9
+
10
+ def output
11
+ $stdout
12
+ end
13
+
3
14
  def log(message = "")
4
- puts message unless $log_level == "error"
15
+ output.puts(message) unless $log_level == "error"
5
16
  end
6
17
 
7
- def abort(message)
8
- raise Dexter::Abort, message
18
+ def colorize(message, color)
19
+ if output.tty?
20
+ "\e[#{COLOR_CODES[color]}m#{message}\e[0m"
21
+ else
22
+ message
23
+ end
9
24
  end
10
25
  end
11
26
  end
@@ -0,0 +1,25 @@
1
+ module Dexter
2
+ class PgStatActivityParser < LogParser
3
+ def perform
4
+ queries = {}
5
+
6
+ loop do
7
+ new_queries = {}
8
+ @logfile.stat_activity.each do |row|
9
+ new_queries[row["id"]] = row
10
+ end
11
+
12
+ # store queries after they complete
13
+ queries.each do |id, row|
14
+ unless new_queries[id]
15
+ process_entry(row["query"], row["duration_ms"].to_f)
16
+ end
17
+ end
18
+
19
+ queries = new_queries
20
+
21
+ sleep(1)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -6,15 +6,19 @@ module Dexter
6
6
  @logfile = logfile
7
7
 
8
8
  @collector = Collector.new(min_time: options[:min_time], min_calls: options[:min_calls])
9
+ @indexer = Indexer.new(options)
10
+
9
11
  @log_parser =
10
- if options[:input_format] == "csv"
12
+ if @logfile == :pg_stat_activity
13
+ PgStatActivityParser.new(@indexer, @collector)
14
+ elsif options[:input_format] == "csv"
11
15
  CsvLogParser.new(logfile, @collector)
16
+ elsif options[:input_format] == "sql"
17
+ SqlLogParser.new(logfile, @collector)
12
18
  else
13
19
  LogParser.new(logfile, @collector)
14
20
  end
15
21
 
16
- @indexer = Indexer.new(options)
17
-
18
22
  @starting_interval = 3
19
23
  @interval = options[:interval]
20
24
 
@@ -25,7 +29,7 @@ module Dexter
25
29
  end
26
30
 
27
31
  def perform
28
- if @logfile == STDIN
32
+ if [STDIN, :pg_stat_activity].include?(@logfile)
29
33
  Thread.abort_on_exception = true
30
34
  Thread.new do
31
35
  sleep(@starting_interval)
@@ -33,7 +37,7 @@ module Dexter
33
37
  begin
34
38
  process_queries
35
39
  rescue PG::ServerError => e
36
- log "ERROR: #{e.class.name}: #{e.message}"
40
+ log colorize("ERROR: #{e.class.name}: #{e.message}", :red)
37
41
  end
38
42
  sleep(@interval)
39
43
  end
@@ -43,7 +47,7 @@ module Dexter
43
47
  begin
44
48
  @log_parser.perform
45
49
  rescue Errno::ENOENT => e
46
- abort "ERROR: #{e.message}"
50
+ raise Dexter::Abort, "ERROR: #{e.message}"
47
51
  end
48
52
 
49
53
  process_queries
@@ -0,0 +1,10 @@
1
+ module Dexter
2
+ class SqlLogParser < LogParser
3
+ def perform
4
+ # TODO support streaming
5
+ @logfile.read.split(";").each do |statement|
6
+ process_entry(statement, 0)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module Dexter
2
- VERSION = "0.3.3"
2
+ VERSION = "0.3.8"
3
3
  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.3
4
+ version: 0.3.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-23 00:00:00.000000000 Z
11
+ date: 2020-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: slop
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.2.0
19
+ version: 4.8.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 4.2.0
26
+ version: 4.8.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: pg
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 0.18.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 0.18.2
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: pg_query
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -95,23 +95,16 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  description:
98
- email:
99
- - andrew@chartkick.com
98
+ email: andrew@chartkick.com
100
99
  executables:
101
100
  - dexter
102
101
  extensions: []
103
102
  extra_rdoc_files: []
104
103
  files:
105
- - ".gitignore"
106
- - ".travis.yml"
107
104
  - CHANGELOG.md
108
- - Gemfile
109
105
  - LICENSE.txt
110
106
  - README.md
111
- - Rakefile
112
107
  - exe/dexter
113
- - guides/Hosted-Postgres.md
114
- - guides/Linux.md
115
108
  - lib/dexter.rb
116
109
  - lib/dexter/client.rb
117
110
  - lib/dexter/collector.rb
@@ -119,12 +112,14 @@ files:
119
112
  - lib/dexter/indexer.rb
120
113
  - lib/dexter/log_parser.rb
121
114
  - lib/dexter/logging.rb
115
+ - lib/dexter/pg_stat_activity_parser.rb
122
116
  - lib/dexter/processor.rb
123
117
  - lib/dexter/query.rb
118
+ - lib/dexter/sql_log_parser.rb
124
119
  - lib/dexter/version.rb
125
- - pgdexter.gemspec
126
120
  homepage: https://github.com/ankane/dexter
127
- licenses: []
121
+ licenses:
122
+ - MIT
128
123
  metadata: {}
129
124
  post_install_message:
130
125
  rdoc_options: []
@@ -134,15 +129,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
134
129
  requirements:
135
130
  - - ">="
136
131
  - !ruby/object:Gem::Version
137
- version: '0'
132
+ version: '2.2'
138
133
  required_rubygems_version: !ruby/object:Gem::Requirement
139
134
  requirements:
140
135
  - - ">="
141
136
  - !ruby/object:Gem::Version
142
137
  version: '0'
143
138
  requirements: []
144
- rubyforge_project:
145
- rubygems_version: 2.6.13
139
+ rubygems_version: 3.1.2
146
140
  signing_key:
147
141
  specification_version: 4
148
142
  summary: The automatic indexer for Postgres
data/.gitignore DELETED
@@ -1,9 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
@@ -1,17 +0,0 @@
1
- language: ruby
2
- rvm: 2.4.1
3
- cache: bundler
4
- script: bundle exec rake test
5
- addons:
6
- postgresql: "9.6"
7
- before_script:
8
- - sudo apt-get install postgresql-server-dev-9.6
9
- - git clone https://github.com/dalibo/hypopg.git
10
- - cd hypopg
11
- - make
12
- - sudo make install
13
- - psql -c 'create database dexter_test;' -U postgres
14
- notifications:
15
- email:
16
- on_success: never
17
- on_failure: change
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in dexter.gemspec
4
- gemspec
data/Rakefile DELETED
@@ -1,11 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
3
-
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
8
- t.warning = false
9
- end
10
-
11
- task default: :test
@@ -1,102 +0,0 @@
1
- # Hosted Postgres
2
-
3
- Some hosted providers like Amazon RDS and Heroku do not support the HypoPG extension, which Dexter needs to run. Hopefully this will change with time. For now, we can spin up a separate database instance to run Dexter. It’s not super convenient, but can be useful to do from time to time.
4
-
5
- ### Install Postgres and Ruby
6
-
7
- Linux
8
-
9
- ```sh
10
- sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
11
- sudo apt-get install -y wget ca-certificates
12
- wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
13
- sudo apt-get update
14
- sudo apt-get install -y postgresql-9.6 postgresql-server-dev-9.6
15
- sudo -u postgres createuser $(whoami) -s
16
- sudo apt-get install -y ruby2.2 ruby2.2-dev
17
- ```
18
-
19
- Mac
20
-
21
- ```sh
22
- brew install postgresql
23
- brew install ruby
24
- ```
25
-
26
- ### Install HypoPG and Dexter
27
-
28
- HypoPG
29
-
30
- ```sh
31
- cd /tmp
32
- curl -L https://github.com/dalibo/hypopg/archive/1.0.0.tar.gz | tar xz
33
- cd hypopg-1.0.0
34
- make
35
- make install # may need sudo
36
- ```
37
-
38
- Dexter
39
-
40
- ```sh
41
- gem install pgdexter # may need sudo
42
- ```
43
-
44
- ### Download logs
45
-
46
- #### Amazon RDS
47
-
48
- Create an IAM user with the policy below:
49
-
50
- ```
51
- {
52
- "Statement": [
53
- {
54
- "Action": [
55
- "rds:DescribeDBLogFiles",
56
- "rds:DownloadDBLogFilePortion"
57
- ],
58
- "Effect": "Allow",
59
- "Resource": "*"
60
- }
61
- ]
62
- }
63
- ```
64
-
65
- And run:
66
-
67
- ```sh
68
- aws configure
69
- gem install pghero_logs # may need sudo
70
- pghero_logs download <instance-id>
71
- ```
72
-
73
- #### Heroku
74
-
75
- Production-tier databases only
76
-
77
- ```sh
78
- heroku logs -p postgres > postgresql.log
79
- ```
80
-
81
- ### Dump and restore
82
-
83
- We recommend creating a new instance from a snapshot for the dump to avoid affecting customers.
84
-
85
- ```sh
86
- pg_dump -v -j 8 -Fd -f /tmp/newout.dir <connection-options>
87
- ```
88
-
89
- Then shutdown the dump instance. Restore with:
90
-
91
- ```sh
92
- createdb dexter_restore
93
- pg_restore -v -j 8 -x -O --format=d -d dexter_restore /tmp/newout.dir/
94
- ```
95
-
96
- ### Run Dexter
97
-
98
- ```sh
99
- dexter dexter_restore postgresql.log* --analyze
100
- ```
101
-
102
- :tada:
@@ -1,70 +0,0 @@
1
- # Linux Packages
2
-
3
- Distributions
4
-
5
- - [Ubuntu 16.04 (Xenial)](#ubuntu-1604-xenial)
6
- - [Ubuntu 14.04 (Trusty)](#ubuntu-1404-trusty)
7
- - [Debian 9 (Stretch)](#debian-9-stretch)
8
- - [Debian 8 (Jesse)](#debian-8-jesse)
9
- - [CentOS / RHEL 7](#centos--rhel-7)
10
- - [SUSE Linux Enterprise Server 12](#suse-linux-enterprise-server-12)
11
-
12
- ### Ubuntu 16.04 (Xenial)
13
-
14
- ```sh
15
- wget -qO- https://dl.packager.io/srv/pghero/dexter/key | sudo apt-key add -
16
- sudo wget -O /etc/apt/sources.list.d/dexter.list \
17
- https://dl.packager.io/srv/pghero/dexter/master/installer/ubuntu/16.04.repo
18
- sudo apt-get update
19
- sudo apt-get -y install dexter
20
- ```
21
-
22
- ### Ubuntu 14.04 (Trusty)
23
-
24
- ```sh
25
- wget -qO- https://dl.packager.io/srv/pghero/dexter/key | sudo apt-key add -
26
- sudo wget -O /etc/apt/sources.list.d/dexter.list \
27
- https://dl.packager.io/srv/pghero/dexter/master/installer/ubuntu/14.04.repo
28
- sudo apt-get update
29
- sudo apt-get install dexter
30
- ```
31
-
32
- ### Debian 9 (Stretch)
33
-
34
- ```sh
35
- wget -qO- https://dl.packager.io/srv/pghero/dexter/key | sudo apt-key add -
36
- sudo wget -O /etc/apt/sources.list.d/dexter.list \
37
- https://dl.packager.io/srv/pghero/dexter/master/installer/debian/9.repo
38
- sudo apt-get update
39
- sudo apt-get install dexter
40
- ```
41
-
42
- ### Debian 8 (Jesse)
43
-
44
- ```sh
45
- wget -qO- https://dl.packager.io/srv/pghero/dexter/key | sudo apt-key add -
46
- sudo wget -O /etc/apt/sources.list.d/dexter.list \
47
- https://dl.packager.io/srv/pghero/dexter/master/installer/debian/8.repo
48
- sudo apt-get update
49
- sudo apt-get install dexter
50
- ```
51
-
52
- ### CentOS / RHEL 7
53
-
54
- ```sh
55
- sudo wget -O /etc/yum.repos.d/dexter.repo \
56
- https://dl.packager.io/srv/pghero/dexter/master/installer/el/7.repo
57
- sudo yum install dexter
58
- ```
59
-
60
- ### SUSE Linux Enterprise Server 12
61
-
62
- ```sh
63
- sudo wget -O /etc/zypp/repos.d/dexter.repo \
64
- https://dl.packager.io/srv/pghero/dexter/master/installer/sles/12.repo
65
- sudo zypper install dexter
66
- ```
67
-
68
- ## Credits
69
-
70
- :heart: Made possible by [Packager](https://packager.io/)
@@ -1,30 +0,0 @@
1
- # coding: utf-8
2
-
3
- lib = File.expand_path("../lib", __FILE__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require "dexter/version"
6
-
7
- Gem::Specification.new do |spec|
8
- spec.name = "pgdexter"
9
- spec.version = Dexter::VERSION
10
- spec.authors = ["Andrew Kane"]
11
- spec.email = ["andrew@chartkick.com"]
12
-
13
- spec.summary = "The automatic indexer for Postgres"
14
- spec.homepage = "https://github.com/ankane/dexter"
15
-
16
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
- f.match(%r{^(test|spec|features)/})
18
- end
19
- spec.bindir = "exe"
20
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
- spec.require_paths = ["lib"]
22
-
23
- spec.add_dependency "slop", ">= 4.2.0"
24
- spec.add_dependency "pg"
25
- spec.add_dependency "pg_query"
26
-
27
- spec.add_development_dependency "bundler"
28
- spec.add_development_dependency "rake"
29
- spec.add_development_dependency "minitest"
30
- end