tobias 0.2.0 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 342518e2c3730a589319c0d16967f7e9fdc80affde011c3820ef2c1034fcbc3e
4
- data.tar.gz: adcdc3152a07d4e1203ad6ceff26901e999f5a8110e883675aa20c9e894ae932
3
+ metadata.gz: 4b50326ed51ec5388f5f63528cbfe0cda2f27db4e505b0cdf1349f1cc9313311
4
+ data.tar.gz: e76ddcd08800005f89ddb772953df045ad623fcfc13e114090bdfcdd49c0dabb
5
5
  SHA512:
6
- metadata.gz: af1341883626cda785ba564368baad44e0cec9e55751bb7c00d204b1e12cf32ebc3817002f2c7fd81e7b57a6ddd2a78be74b11fa0e1f3f837741552f8064f338
7
- data.tar.gz: 58d79c4c82d5bf1cd21e42c6770dcc454c52bfab5a22a5fa1d15ed6dfe8b3abfd340d7a31b07c6205b7955787214395814176cd997307e2dad083256c29c5139
6
+ metadata.gz: 456a7a52310fcac42a5755ad02aeb464bb429742888a195569fb5d5df7f33416d583cef442eca92b035af9807e7ec5d17c626b0eda20b9d43998fc4c99ccb6f3
7
+ data.tar.gz: e8c5d112db7cd2e87f37455d90b25a6dfe53eba27e60a492c4fbc4798a941bdc9b69532b813eb39f4c69b68c34812e09112ec19a80485f90e526869e2d0c1794
data/lib/tobias/cli.rb CHANGED
@@ -10,7 +10,7 @@ module Tobias
10
10
 
11
11
  desc "profile SCRIPT", "profile"
12
12
  option :database_url, type: :string, required: true
13
- option :iterations, type: :numeric, default: 100
13
+ option :iterations, type: :numeric, default: 10
14
14
  option :debug, type: :boolean, default: false
15
15
  def profile(script)
16
16
  database = Sequel.connect(options[:database_url])
@@ -24,7 +24,6 @@ module Tobias
24
24
  end
25
25
 
26
26
  container = Container.new(code)
27
- work_mems = WorkMem.valid_for(database)
28
27
  results = {}
29
28
 
30
29
  parsed = TTY::Markdown.parse(<<~MARKDOWN)
@@ -33,11 +32,7 @@ module Tobias
33
32
  puts parsed
34
33
 
35
34
  thinking_time = Benchmark.realtime do
36
- container.queries.each do |name, block|
37
- work_mem = Evaluation.new(database, work_mems).run(options, &block)
38
-
39
- results[name] = work_mem
40
- end
35
+ results = Evaluations.run(database, container, options)
41
36
  end
42
37
 
43
38
  parsed = TTY::Markdown.parse(<<~MARKDOWN)
@@ -45,18 +40,7 @@ module Tobias
45
40
 
46
41
  I thought about your queries for precisely #{thinking_time.round(2)} seconds and here is what I recommend:
47
42
 
48
- | Query | Required work_mem |
49
- |-------|-------------------|
50
- #{results.map { |name, work_mem| "| #{name} | #{work_mem.to_sql} |" }.join("\n")}
51
-
52
- Your application will need to run with at least #{results.values.max.to_sql} of work_mem.
53
-
54
- To apply my recommendations, run the following SQL:
55
-
56
- ```sql
57
- ALTER SYSTEM SET work_mem = '#{results.values.max.to_sql}';
58
- SELECT pg_reload_conf();
59
- ```
43
+ #{results.join("\n")}
60
44
 
61
45
  Regards,
62
46
  ~ Tobias
@@ -17,4 +17,4 @@ module Tobias
17
17
  @queries[name] = block
18
18
  end
19
19
  end
20
- end
20
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tobias
4
+ module Evaluations
5
+ class Base
6
+ attr_reader :database, :container
7
+
8
+ def initialize(database, container)
9
+ @database = database
10
+ @container = container
11
+ end
12
+
13
+ def run(options, &block)
14
+ results = {}
15
+
16
+ container.queries.each do |name, query|
17
+ results.merge!(run_each(name, query, options))
18
+ end
19
+
20
+ to_markdown(results)
21
+ end
22
+
23
+ def run_each(query, options)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def to_markdown(results)
28
+ raise NotImplementedError
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tobias
4
+ module Evaluations
5
+ class WorkMem < Base
6
+
7
+ def work_mems
8
+ Tobias::WorkMem.valid_for(database)
9
+ end
10
+
11
+ def description
12
+ "Optional work_mem settings"
13
+ end
14
+
15
+ def run_each(name, query, options)
16
+ work_mems.each do |value|
17
+ database.transaction do
18
+ database.run("CREATE EXTENSION IF NOT EXISTS pg_stat_statements")
19
+ database.run("SET LOCAL work_mem = '#{value.to_sql}'")
20
+ database.select(Sequel.function(:pg_stat_reset)).first
21
+
22
+ query_result = database.instance_eval(&query)
23
+ options[:iterations].to_i.times do
24
+ database.run(query_result.sql)
25
+ end
26
+
27
+ stats = database[:pg_stat_database].where(datname: Sequel.function(:current_database)).first
28
+
29
+ if stats[:temp_files] == 0 && stats[:temp_bytes] == 0
30
+ return { name => value }
31
+ end
32
+ end
33
+ end
34
+
35
+ # TODO: Add a warning message or something.
36
+ return { name => nil }
37
+ end
38
+
39
+ def to_markdown(results)
40
+ current_work_mem = Tobias::WorkMem.from_sql(database.fetch("SHOW work_mem").first[:work_mem])
41
+
42
+ <<~MARKDOWN
43
+ ## #{description}
44
+
45
+ | Query | Required work_mem |
46
+ |-------|-------------------|
47
+ #{results.map { |name, work_mem| "| #{name} | #{work_mem.to_sql} |" }.join("\n")}
48
+
49
+ Your application will need to run with at least #{results.values.max.to_sql} of work_mem.
50
+
51
+ I see that your current work_mem setting is #{current_work_mem.to_sql}.
52
+
53
+ To apply my recommendations, run the following SQL:
54
+
55
+ ```sql
56
+ ALTER SYSTEM SET work_mem = '#{results.values.max.to_sql}';
57
+ SELECT pg_reload_conf();
58
+ ```
59
+ MARKDOWN
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tobias
4
+ module Evaluations
5
+ def self.run(database, container, options)
6
+ results = []
7
+ results << WorkMem.new(database, container).run(options)
8
+ results
9
+ end
10
+ end
11
+ end
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tobias
4
- VERSION = "0.2.0"
5
-
4
+ VERSION = "0.3.0"
6
5
  SUMMARY = "Tobias is a tool to help you find the optimal work_mem for your queries."
7
6
  DESCRIPTION = "Tobias is a tool to help you find the optimal work_mem for your queries."
8
7
  end
@@ -4,6 +4,21 @@ module Tobias
4
4
  class WorkMem
5
5
  attr_reader :amount
6
6
 
7
+ def self.from_sql(sql)
8
+ case sql
9
+ when /^\d+B$/
10
+ new(sql.to_i)
11
+ when /^\d+kB$/
12
+ new(sql.to_i * 1024)
13
+ when /^\d+MB$/
14
+ new(sql.to_i * 1024 * 1024)
15
+ when /^\d+GB$/
16
+ new(sql.to_i * 1024 * 1024 * 1024)
17
+ else
18
+ raise "Invalid work_mem setting: #{sql}"
19
+ end
20
+ end
21
+
7
22
  def initialize(amount)
8
23
  @amount = amount
9
24
  end
@@ -52,6 +67,7 @@ module Tobias
52
67
  [
53
68
  new(64.kilobytes),
54
69
  new(128.kilobytes),
70
+ new(256.kilobytes),
55
71
  new(512.kilobytes),
56
72
  new(1.megabyte),
57
73
  new(4.megabytes),
@@ -101,4 +117,4 @@ module Tobias
101
117
  self.all.select { |work_mem| work_mem.amount < bytes_per_connection.to_i }
102
118
  end
103
119
  end
104
- end
120
+ end
data/lib/tobias.rb CHANGED
@@ -23,9 +23,12 @@ require "tty-markdown"
23
23
 
24
24
  $LOAD_PATH.unshift File.dirname(__FILE__)
25
25
 
26
+ require "tobias/evaluations"
27
+ require "tobias/evaluations/base"
28
+ require "tobias/evaluations/work_mem"
29
+
26
30
  module Tobias
27
31
  autoload :CLI, "tobias/cli"
28
32
  autoload :Container, "tobias/container"
29
- autoload :Evaluation, "tobias/evaluation"
30
33
  autoload :WorkMem, "tobias/work_mem"
31
- end
34
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tobias
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Daniel
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-08-03 00:00:00.000000000 Z
10
+ date: 2025-08-04 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -240,7 +240,9 @@ files:
240
240
  - lib/tobias.rb
241
241
  - lib/tobias/cli.rb
242
242
  - lib/tobias/container.rb
243
- - lib/tobias/evaluation.rb
243
+ - lib/tobias/evaluations.rb
244
+ - lib/tobias/evaluations/base.rb
245
+ - lib/tobias/evaluations/work_mem.rb
244
246
  - lib/tobias/version.rb
245
247
  - lib/tobias/work_mem.rb
246
248
  homepage: https://github.com/binarycleric/tobias
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Tobias
4
- class Evaluation
5
- attr_reader :database
6
-
7
- def initialize(database, work_mems)
8
- @database = database
9
- @work_mems = work_mems
10
- end
11
-
12
- def run(options, &block)
13
- @work_mems.each do |value|
14
- database.transaction do
15
- database.run("CREATE EXTENSION IF NOT EXISTS pg_stat_statements")
16
- database.run("SET LOCAL work_mem = '#{value.to_sql}'")
17
- database.select(Sequel.function(:pg_stat_reset)).first
18
- database.instance_eval(&block)
19
-
20
- query = database.instance_eval(&block)
21
- options[:iterations].to_i.times do
22
- database.run(query.sql)
23
- end
24
-
25
- stats = database[:pg_stat_database].where(datname: Sequel.function(:current_database)).first
26
-
27
- if stats[:temp_files] == 0 && stats[:temp_bytes] == 0
28
- return value
29
- end
30
- end
31
- end
32
-
33
- raise "No work_mem found."
34
- end
35
- end
36
- end