tobias 0.1.0 → 0.2.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 +4 -4
- data/lib/tobias/cli.rb +38 -45
- data/lib/tobias/container.rb +20 -0
- data/lib/tobias/evaluation.rb +28 -1
- data/lib/tobias/version.rb +4 -1
- data/lib/tobias/work_mem.rb +55 -1
- data/lib/tobias.rb +2 -0
- metadata +14 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 342518e2c3730a589319c0d16967f7e9fdc80affde011c3820ef2c1034fcbc3e
|
4
|
+
data.tar.gz: adcdc3152a07d4e1203ad6ceff26901e999f5a8110e883675aa20c9e894ae932
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af1341883626cda785ba564368baad44e0cec9e55751bb7c00d204b1e12cf32ebc3817002f2c7fd81e7b57a6ddd2a78be74b11fa0e1f3f837741552f8064f338
|
7
|
+
data.tar.gz: 58d79c4c82d5bf1cd21e42c6770dcc454c52bfab5a22a5fa1d15ed6dfe8b3abfd340d7a31b07c6205b7955787214395814176cd997307e2dad083256c29c5139
|
data/lib/tobias/cli.rb
CHANGED
@@ -11,9 +11,10 @@ module Tobias
|
|
11
11
|
desc "profile SCRIPT", "profile"
|
12
12
|
option :database_url, type: :string, required: true
|
13
13
|
option :iterations, type: :numeric, default: 100
|
14
|
+
option :debug, type: :boolean, default: false
|
14
15
|
def profile(script)
|
15
16
|
database = Sequel.connect(options[:database_url])
|
16
|
-
database.loggers << Logger.new(
|
17
|
+
database.loggers << Logger.new(STDERR) if options[:debug]
|
17
18
|
database.extension :pg_json
|
18
19
|
|
19
20
|
if File.exist?(script)
|
@@ -22,54 +23,46 @@ module Tobias
|
|
22
23
|
raise "Script not found at: #{script}"
|
23
24
|
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
database.run(query.sql)
|
40
|
-
end
|
41
|
-
times << time
|
42
|
-
end
|
43
|
-
|
44
|
-
stats = database[:pg_stat_database].where(datname: Sequel.function(:current_database)).first
|
45
|
-
|
46
|
-
puts "--------------------------------"
|
47
|
-
puts "query: #{name}"
|
48
|
-
puts "work_mem: #{value.to_sql}"
|
49
|
-
puts "clock time (mean): #{times.mean.round(2)}"
|
50
|
-
puts "clock time (95%): #{times.percentile(95).round(2)}"
|
51
|
-
|
52
|
-
if stats[:temp_files] > 0 || stats[:temp_bytes] > 0
|
53
|
-
puts "Not enough work_mem"
|
54
|
-
puts "temp_files: #{stats[:temp_files]}"
|
55
|
-
puts "temp_bytes: #{stats[:temp_bytes]}"
|
56
|
-
else
|
57
|
-
puts "No temporary files written."
|
58
|
-
puts "Current work_mem: '#{value.to_sql}' is sufficient."
|
59
|
-
return
|
60
|
-
end
|
61
|
-
puts "--------------------------------"
|
62
|
-
end
|
26
|
+
container = Container.new(code)
|
27
|
+
work_mems = WorkMem.valid_for(database)
|
28
|
+
results = {}
|
29
|
+
|
30
|
+
parsed = TTY::Markdown.parse(<<~MARKDOWN)
|
31
|
+
# @tobias is thinking...
|
32
|
+
MARKDOWN
|
33
|
+
puts parsed
|
34
|
+
|
35
|
+
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
|
63
40
|
end
|
64
41
|
end
|
65
|
-
end
|
66
42
|
|
67
|
-
|
43
|
+
parsed = TTY::Markdown.parse(<<~MARKDOWN)
|
44
|
+
# @tobias has sent you a new message
|
68
45
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
46
|
+
I thought about your queries for precisely #{thinking_time.round(2)} seconds and here is what I recommend:
|
47
|
+
|
48
|
+
| Query | Required work_mem |
|
49
|
+
|-------|-------------------|
|
50
|
+
#{results.map { |name, work_mem| "| #{name} | #{work_mem.to_sql} |" }.join("\n")}
|
73
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
|
+
```
|
60
|
+
|
61
|
+
Regards,
|
62
|
+
~ Tobias
|
63
|
+
MARKDOWN
|
64
|
+
|
65
|
+
puts parsed
|
66
|
+
end
|
74
67
|
end
|
75
68
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tobias
|
4
|
+
class Container
|
5
|
+
def initialize(code)
|
6
|
+
@code = code
|
7
|
+
@queries = {}
|
8
|
+
|
9
|
+
eval(code, binding, __FILE__, __LINE__)
|
10
|
+
end
|
11
|
+
|
12
|
+
def queries
|
13
|
+
@queries
|
14
|
+
end
|
15
|
+
|
16
|
+
def query(name, &block)
|
17
|
+
@queries[name] = block
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/tobias/evaluation.rb
CHANGED
@@ -2,8 +2,35 @@
|
|
2
2
|
|
3
3
|
module Tobias
|
4
4
|
class Evaluation
|
5
|
-
|
5
|
+
attr_reader :database
|
6
|
+
|
7
|
+
def initialize(database, work_mems)
|
6
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."
|
7
34
|
end
|
8
35
|
end
|
9
36
|
end
|
data/lib/tobias/version.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Tobias
|
4
|
-
VERSION = "0.
|
4
|
+
VERSION = "0.2.0"
|
5
|
+
|
6
|
+
SUMMARY = "Tobias is a tool to help you find the optimal work_mem for your queries."
|
7
|
+
DESCRIPTION = "Tobias is a tool to help you find the optimal work_mem for your queries."
|
5
8
|
end
|
data/lib/tobias/work_mem.rb
CHANGED
@@ -2,10 +2,32 @@
|
|
2
2
|
|
3
3
|
module Tobias
|
4
4
|
class WorkMem
|
5
|
+
attr_reader :amount
|
6
|
+
|
5
7
|
def initialize(amount)
|
6
8
|
@amount = amount
|
7
9
|
end
|
8
10
|
|
11
|
+
def >(other)
|
12
|
+
@amount > other.amount
|
13
|
+
end
|
14
|
+
|
15
|
+
def <(other)
|
16
|
+
@amount < other.amount
|
17
|
+
end
|
18
|
+
|
19
|
+
def >=(other)
|
20
|
+
@amount >= other.amount
|
21
|
+
end
|
22
|
+
|
23
|
+
def <=(other)
|
24
|
+
@amount <= other.amount
|
25
|
+
end
|
26
|
+
|
27
|
+
def <=>(other)
|
28
|
+
@amount <=> other.amount
|
29
|
+
end
|
30
|
+
|
9
31
|
def to_sql
|
10
32
|
case @amount
|
11
33
|
when 0...1024
|
@@ -44,7 +66,39 @@ module Tobias
|
|
44
66
|
new(2.gigabytes),
|
45
67
|
new(4.gigabytes),
|
46
68
|
new(8.gigabytes),
|
47
|
-
]
|
69
|
+
].sort_by(&:amount)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Inspects the database to determine the valid work_mem settings for the current user.
|
73
|
+
# We'll need at least connection_limit / effective_cache_size for an optimal
|
74
|
+
# work_mem setting, otherwise a user could run out of memory at max connections.
|
75
|
+
def self.valid_for(database)
|
76
|
+
role_conn_limit = database.select(:rolconnlimit).
|
77
|
+
from(:pg_roles).
|
78
|
+
where(rolname: Sequel.lit("current_user")).
|
79
|
+
first
|
80
|
+
|
81
|
+
max_connections = database.select(:setting).
|
82
|
+
from(:pg_settings).
|
83
|
+
where(name: "max_connections").
|
84
|
+
first
|
85
|
+
|
86
|
+
effective_cache_size = database.select(:setting, :unit).
|
87
|
+
from(:pg_settings).
|
88
|
+
where(name: "effective_cache_size").
|
89
|
+
first
|
90
|
+
|
91
|
+
effective_cache_size_bytes = effective_cache_size[:setting].to_i * 8 * 1024
|
92
|
+
|
93
|
+
connection_limit = if role_conn_limit[:rolconnlimit] > 0
|
94
|
+
role_conn_limit[:rolconnlimit]
|
95
|
+
else
|
96
|
+
max_connections[:setting].to_i
|
97
|
+
end
|
98
|
+
|
99
|
+
bytes_per_connection = effective_cache_size_bytes / connection_limit
|
100
|
+
|
101
|
+
self.all.select { |work_mem| work_mem.amount < bytes_per_connection.to_i }
|
48
102
|
end
|
49
103
|
end
|
50
104
|
end
|
data/lib/tobias.rb
CHANGED
@@ -19,11 +19,13 @@ require "active_support/all"
|
|
19
19
|
require "sequel"
|
20
20
|
require "enumerable-stats"
|
21
21
|
require "benchmark"
|
22
|
+
require "tty-markdown"
|
22
23
|
|
23
24
|
$LOAD_PATH.unshift File.dirname(__FILE__)
|
24
25
|
|
25
26
|
module Tobias
|
26
27
|
autoload :CLI, "tobias/cli"
|
28
|
+
autoload :Container, "tobias/container"
|
27
29
|
autoload :Evaluation, "tobias/evaluation"
|
28
30
|
autoload :WorkMem, "tobias/work_mem"
|
29
31
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tobias
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Daniel
|
@@ -210,20 +210,26 @@ dependencies:
|
|
210
210
|
- !ruby/object:Gem::Version
|
211
211
|
version: 1.3.0
|
212
212
|
- !ruby/object:Gem::Dependency
|
213
|
-
name:
|
213
|
+
name: tty-markdown
|
214
214
|
requirement: !ruby/object:Gem::Requirement
|
215
215
|
requirements:
|
216
216
|
- - "~>"
|
217
217
|
- !ruby/object:Gem::Version
|
218
|
-
version: '
|
219
|
-
|
218
|
+
version: '0.7'
|
219
|
+
- - ">="
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: 0.7.0
|
222
|
+
type: :runtime
|
220
223
|
prerelease: false
|
221
224
|
version_requirements: !ruby/object:Gem::Requirement
|
222
225
|
requirements:
|
223
226
|
- - "~>"
|
224
227
|
- !ruby/object:Gem::Version
|
225
|
-
version: '
|
226
|
-
|
228
|
+
version: '0.7'
|
229
|
+
- - ">="
|
230
|
+
- !ruby/object:Gem::Version
|
231
|
+
version: 0.7.0
|
232
|
+
description: Tobias is a tool to help you find the optimal work_mem for your queries.
|
227
233
|
email: binarycleric@gmail.com
|
228
234
|
executables:
|
229
235
|
- tobias
|
@@ -233,6 +239,7 @@ files:
|
|
233
239
|
- bin/tobias
|
234
240
|
- lib/tobias.rb
|
235
241
|
- lib/tobias/cli.rb
|
242
|
+
- lib/tobias/container.rb
|
236
243
|
- lib/tobias/evaluation.rb
|
237
244
|
- lib/tobias/version.rb
|
238
245
|
- lib/tobias/work_mem.rb
|
@@ -258,5 +265,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
258
265
|
requirements: []
|
259
266
|
rubygems_version: 3.6.2
|
260
267
|
specification_version: 4
|
261
|
-
summary: Tobias
|
268
|
+
summary: Tobias is a tool to help you find the optimal work_mem for your queries.
|
262
269
|
test_files: []
|