mysql_alter_monitoring 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 22f662537cec8e934e80799545292630642eb0062266f48bca2a669121cb7a04
4
+ data.tar.gz: 5bb3d3069b2d221b36321528df3a7299bffa4e56c1808e28c89305fef7d81c21
5
+ SHA512:
6
+ metadata.gz: d821d17de14ed007b75be479c1edf02fa401a09e040c6a922eb676212ed966d2f5734fb3bfefe987ad9a30e6c98bcb90a82857f5d580fb4965f1633c6e26144a
7
+ data.tar.gz: cd6a171834427e6738814bc45a751cacbc67133512835af17947e62f54ac78309fbbe86da125c65966b62ea6a138dda1b90435310122446125195a9be27d5fb6
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,18 @@
1
+ require:
2
+ - rubocop-performance
3
+ - rubocop-rake
4
+ - rubocop-rspec
5
+ - rubocop-thread_safety
6
+
7
+ AllCops:
8
+ TargetRubyVersion: 2.6
9
+ NewCops: enable
10
+
11
+ Layout/LineLength:
12
+ Max: 120
13
+
14
+ RSpec/MultipleExpectations:
15
+ Enabled: false
16
+
17
+ RSpec/ExampleLength:
18
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-02-11
4
+
5
+ - Initial release
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at 52433677+akito-fujisaki@users.noreply.github.com. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Dockerfile ADDED
@@ -0,0 +1,18 @@
1
+ FROM ruby:3.2.0-slim
2
+
3
+ RUN apt-get update && \
4
+ apt-get install -y --no-install-recommends \
5
+ build-essential \
6
+ git \
7
+ libmariadb-dev-compat \
8
+ libmariadb-dev \
9
+ mariadb-client && \
10
+ apt-get clean
11
+
12
+ WORKDIR /src
13
+
14
+ COPY bin/setup ./bin/
15
+ COPY Gemfile Gemfile.lock mysql_alter_monitoring.gemspec ./
16
+ COPY lib/mysql_alter_monitoring/version.rb ./lib/mysql_alter_monitoring/
17
+
18
+ RUN bundle install -j4
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in mysql_alter_monitoring.gemspec
6
+ gemspec
7
+
8
+ gem 'rake'
9
+ gem 'rspec'
10
+ gem 'rubocop'
11
+ gem 'rubocop-performance'
12
+ gem 'rubocop-rake'
13
+ gem 'rubocop-rspec'
14
+ gem 'rubocop-thread_safety'
15
+ gem 'yard'
data/Gemfile.lock ADDED
@@ -0,0 +1,80 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mysql_alter_monitoring (0.1.0)
5
+ mysql2
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ diff-lcs (1.5.0)
12
+ json (2.6.3)
13
+ mysql2 (0.5.5)
14
+ parallel (1.22.1)
15
+ parser (3.2.1.0)
16
+ ast (~> 2.4.1)
17
+ rainbow (3.1.1)
18
+ rake (13.0.6)
19
+ regexp_parser (2.7.0)
20
+ rexml (3.2.5)
21
+ rspec (3.12.0)
22
+ rspec-core (~> 3.12.0)
23
+ rspec-expectations (~> 3.12.0)
24
+ rspec-mocks (~> 3.12.0)
25
+ rspec-core (3.12.1)
26
+ rspec-support (~> 3.12.0)
27
+ rspec-expectations (3.12.2)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.12.0)
30
+ rspec-mocks (3.12.3)
31
+ diff-lcs (>= 1.2.0, < 2.0)
32
+ rspec-support (~> 3.12.0)
33
+ rspec-support (3.12.0)
34
+ rubocop (1.45.1)
35
+ json (~> 2.3)
36
+ parallel (~> 1.10)
37
+ parser (>= 3.2.0.0)
38
+ rainbow (>= 2.2.2, < 4.0)
39
+ regexp_parser (>= 1.8, < 3.0)
40
+ rexml (>= 3.2.5, < 4.0)
41
+ rubocop-ast (>= 1.24.1, < 2.0)
42
+ ruby-progressbar (~> 1.7)
43
+ unicode-display_width (>= 2.4.0, < 3.0)
44
+ rubocop-ast (1.24.1)
45
+ parser (>= 3.1.1.0)
46
+ rubocop-capybara (2.17.0)
47
+ rubocop (~> 1.41)
48
+ rubocop-performance (1.16.0)
49
+ rubocop (>= 1.7.0, < 2.0)
50
+ rubocop-ast (>= 0.4.0)
51
+ rubocop-rake (0.6.0)
52
+ rubocop (~> 1.0)
53
+ rubocop-rspec (2.18.1)
54
+ rubocop (~> 1.33)
55
+ rubocop-capybara (~> 2.17)
56
+ rubocop-thread_safety (0.4.4)
57
+ rubocop (>= 0.53.0)
58
+ ruby-progressbar (1.11.0)
59
+ unicode-display_width (2.4.2)
60
+ webrick (1.7.0)
61
+ yard (0.9.28)
62
+ webrick (~> 1.7.0)
63
+
64
+ PLATFORMS
65
+ aarch64-linux
66
+ arm64-darwin-21
67
+
68
+ DEPENDENCIES
69
+ mysql_alter_monitoring!
70
+ rake
71
+ rspec
72
+ rubocop
73
+ rubocop-performance
74
+ rubocop-rake
75
+ rubocop-rspec
76
+ rubocop-thread_safety
77
+ yard
78
+
79
+ BUNDLED WITH
80
+ 2.4.6
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Akito Fujisaki
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # MysqlAlterMonitoring
2
+
3
+ Monitoring ALTER TABLE Progress for InnoDB Tables Using Performance Schema.
4
+
5
+ See: https://dev.mysql.com/doc/refman/8.0/en/monitor-alter-table-performance-schema.html
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```
12
+ gem 'mysql_alter_monitoring'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```
18
+ $ bundle install
19
+ ```
20
+
21
+ Or install it yourself as:
22
+
23
+ ```
24
+ $ gem install mysql_alter_monitoring
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### Command line
30
+
31
+ All commands require the specification of host, user name, password.
32
+
33
+ It can also be specified in URL format using the --url option.
34
+
35
+ #### Enable monitoring settings for the performance schema
36
+
37
+ ```
38
+ $ mysql-alter-monitoring enable --url mysql2://root:pass@localhost:3306
39
+
40
+ # When specifying host, user, password, etc. separately
41
+ $ mysql-alter-monitoring enable --host localhost --port 3306 --user root --password pass
42
+ ```
43
+
44
+ #### Disable monitoring settings for the performance schema
45
+
46
+ ```
47
+ $ mysql-alter-monitoring disable --url mysql2://root:pass@localhost:3306
48
+ ```
49
+
50
+ #### Start monitoring until event empty
51
+
52
+ ```
53
+ $ mysql-alter-monitoring run --url mysql2://root:pass@localhost:3306
54
+ ```
55
+
56
+ #### Start monitoring forever
57
+
58
+ ```
59
+ $ mysql-alter-monitoring run-forever --url mysql2://root:pass@localhost:3306
60
+ ```
61
+
62
+ #### Show help
63
+
64
+ ```
65
+ $ mysql-alter-monitoring -h
66
+ ```
67
+
68
+ ## Development
69
+
70
+ To develop on docker, build first.
71
+
72
+ ```
73
+ $ docker compose build
74
+ ```
75
+
76
+ Enter docker container and run rake task etc.
77
+
78
+ ```
79
+ $ docker compose run --rm gem bash
80
+ ```
81
+
82
+ ### rspec
83
+
84
+ ```
85
+ # Run all specs
86
+ $ bin/rake spec
87
+
88
+ # Run specs individually
89
+ $ bin/rspec spec/mysql_alter_monitoring/monitor_spec.rb
90
+ ```
91
+
92
+ ### rubocop
93
+
94
+ ```
95
+ # Run lint
96
+ $ bin/rake rubocop
97
+
98
+ # Run rubocop autocorrect
99
+ $ bin/rake lint:fix
100
+ ```
101
+
102
+ ### yard
103
+
104
+ ```
105
+ # Generate and Check code documents
106
+ # Output to doc/index.html
107
+ $ bin/rake doc
108
+ ```
109
+
110
+ ## Contributing
111
+
112
+ Bug reports and pull requests are welcome on GitHub at https://github.com/akito-fujisaki/mysql_alter_monitoring.
113
+
114
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/akito-fujisaki/mysql_alter_monitoring/blob/main/CODE_OF_CONDUCT.md).
115
+
116
+ ## License
117
+
118
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
119
+
120
+ ## Code of Conduct
121
+
122
+ Everyone interacting in the MysqlAlterMonitoring project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/akito-fujisaki/mysql_alter_monitoring/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+ RuboCop::RakeTask.new(:lint) do |t|
10
+ t.options = %w[--parallel]
11
+ end
12
+ namespace :lint do
13
+ desc 'Lint fix (Rubocop)'
14
+ task fix: :autocorrect
15
+ end
16
+
17
+ desc 'Generate and Check code documents'
18
+ task :doc do
19
+ require 'yard'
20
+ YARD::CLI::CommandParser.run
21
+ output = `yard`.lines(chomp: true)
22
+ exit(1) if output.first.include?('[warn]') || output.last.match(/\d+/)[0].to_i != 100
23
+ end
24
+
25
+ task default: %i[spec lint doc]
data/compose.yml ADDED
@@ -0,0 +1,22 @@
1
+ version: "3"
2
+
3
+ services:
4
+ db:
5
+ image: mysql:8.0
6
+ environment:
7
+ - MYSQL_ROOT_PASSWORD=pass
8
+ volumes:
9
+ - db-data:/var/lib/mysql
10
+ gem:
11
+ build: .
12
+ image: mysql-alter-logging
13
+ volumes:
14
+ - .:/src
15
+ environment:
16
+ - DATABASE_URL=mysql2://root:pass@db
17
+ depends_on:
18
+ - db
19
+
20
+ volumes:
21
+ db-data:
22
+ driver: local
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'mysql_alter_monitoring'
5
+
6
+ MysqlAlterMonitoring::CLI.new(ARGV).run
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ module MysqlAlterMonitoring
6
+ # MysqlAlterMonitoring::CLI
7
+ class CLI
8
+ # @param args [Array<String>]
9
+ # @param io [::IO]
10
+ # @return [void]
11
+ def initialize(args, io: $stdout)
12
+ @args = args
13
+ @io = io
14
+ @options = {}
15
+ end
16
+
17
+ # @return [void]
18
+ # @raise [MysqlAlterMonitoring::Error]
19
+ def run # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
20
+ parse!
21
+
22
+ return if help? || version?
23
+
24
+ client = Client.new(build_config)
25
+
26
+ case options[:command]
27
+ when 'enable'
28
+ PerformanceSchemaSetting.new(client, io).enable!
29
+ when 'disable'
30
+ PerformanceSchemaSetting.new(client, io).disable!
31
+ when 'run'
32
+ Monitor.new(client, io).run_until_empty_event
33
+ when 'run-forever'
34
+ Monitor.new(client, io).run_forever
35
+ else
36
+ raise ArgumentError, "Invalid command: #{options[:command]}"
37
+ end
38
+ rescue OptionParser::InvalidOption, ArgumentError => e
39
+ io.puts e.message
40
+ io.puts help
41
+ rescue Interrupt
42
+ io.puts 'Bye!'
43
+ end
44
+
45
+ # @return [String]
46
+ def help
47
+ parser.help
48
+ end
49
+
50
+ private
51
+
52
+ # @!attribute [r] args
53
+ # @return [Array<String>]
54
+ attr_reader :args
55
+ # @!attribute [r] options
56
+ # @return [Hash]
57
+ attr_reader :options
58
+ # @!attribute [r] io
59
+ # @return [::IO]
60
+ attr_reader :io
61
+
62
+ # @return [OptionParser]
63
+ def parser # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
64
+ @parser ||= OptionParser.new do |opts|
65
+ opts.banner = 'Usage: mysql-alter-monitoring [command] [options]'
66
+ opts.separator <<~COMMANDS
67
+ Commands:
68
+ enable Enable monitoring settings for the performance schema
69
+ disable Disable monitoring settings for the performance schema
70
+ run Start monitoring until event empty
71
+ run-forever Start monitoring forever
72
+ COMMANDS
73
+ opts.separator('Options:')
74
+ opts.on('--url VALUE', String, 'example: mysql2://root:pass@localhost:3306') { @options[:url] = _1 }
75
+ opts.on('--host VALUE', String) { @options[:host] = _1 }
76
+ opts.on('--port VALUE', Integer, "default: #{Config::DEFAULT_PORT}") { @options[:port] = _1 }
77
+ opts.on('--user VALUE', String) { @options[:user] = _1 }
78
+ opts.on('--password VALUE', String) { @options[:password] = _1 }
79
+ opts.on_tail('-h', '--help', 'Show help') { @options[:help] = true }
80
+ opts.on_tail('--version', 'Show version') { @options[:version] = true }
81
+ end
82
+ end
83
+
84
+ # @return [Boolean]
85
+ def help?
86
+ options.key?(:help).tap { io.puts help if _1 }
87
+ end
88
+
89
+ # @return [Boolean]
90
+ def version?
91
+ options.key?(:version).tap { io.puts "MysqlAlterMonitoring #{MysqlAlterMonitoring::VERSION}" if _1 }
92
+ end
93
+
94
+ # @return [MysqlAlterMonitoring::Config]
95
+ def build_config
96
+ return Config.build_by_url(options[:url] || '') if options.key?(:url)
97
+
98
+ Config.new(
99
+ host: options[:host] || '',
100
+ port: options[:port] || 0,
101
+ user: options[:user] || '',
102
+ password: options[:password] || ''
103
+ )
104
+ end
105
+
106
+ # @return [Hash]
107
+ # @raise OptionParser::InvalidOption
108
+ def parse!
109
+ command = parser.parse(args).first || ''
110
+ @options.merge!(command: command)
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MysqlAlterMonitoring
4
+ # MysqlAlterMonitoring::Client
5
+ class Client
6
+ extend Forwardable
7
+
8
+ class << self
9
+ # @param url [String]
10
+ # @return [MysqlAlterMonitoring::Client]
11
+ def build_by_url(url)
12
+ Config.build_by_url(url).then { new(_1) }
13
+ end
14
+ end
15
+
16
+ # @!method query
17
+ # @return [Object]
18
+ delegate query: :mysql2_client
19
+
20
+ # @param config [MysqlAlterMonitoring::Config]
21
+ # @return [void]
22
+ def initialize(config)
23
+ @mysql2_client = Mysql2::Client.new(
24
+ host: config.host,
25
+ port: config.port,
26
+ username: config.user,
27
+ password: config.password
28
+ )
29
+ end
30
+
31
+ private
32
+
33
+ # @!attribute [r] mysql2_client
34
+ # @return [Mysql2::Client]
35
+ attr_reader :mysql2_client
36
+ end
37
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MysqlAlterMonitoring
4
+ # MysqlAlterMonitoring::Config
5
+ class Config
6
+ # @return [String]
7
+ SUPPROTED_URI_SCHEME = 'mysql2'
8
+ private_constant :SUPPROTED_URI_SCHEME
9
+ # @return [Integer]
10
+ DEFAULT_PORT = 3306
11
+ public_constant :DEFAULT_PORT
12
+
13
+ class << self
14
+ # Build config in DATABSE_URL format used in Rails.
15
+ # URI scheme must be mysql2.
16
+ # Example: mysql2://root:pass@0.0.0.0:3306/foo?pool=5
17
+ #
18
+ # @param url [String]
19
+ # @return [MysqlAlterMonitoring::Config]
20
+ def build_by_url(url)
21
+ uri = URI.parse(url)
22
+
23
+ raise ArgumentError, 'URL scheme must be mysql2' if uri.scheme != SUPPROTED_URI_SCHEME
24
+
25
+ new(
26
+ host: uri.hostname,
27
+ port: uri.port,
28
+ user: uri.user,
29
+ password: uri.password
30
+ )
31
+ end
32
+ end
33
+
34
+ # @!attribute [r] host
35
+ # @return [String]
36
+ attr_reader :host
37
+ # @!attribute [r] port
38
+ # @return [Integer]
39
+ attr_reader :port
40
+ # @!attribute [r] user
41
+ # @return [String]
42
+ attr_reader :user
43
+ # @!attribute [r] password
44
+ # @return [String]
45
+ attr_reader :password
46
+
47
+ # @param host [String]
48
+ # @param port [Integer]
49
+ # @param user [String]
50
+ # @param password [String]
51
+ # @return [void]
52
+ # @raise [ArgumentError]
53
+ def initialize(host:, port:, user:, password:)
54
+ @host = host
55
+ @port = port || DEFAULT_PORT
56
+ @user = user
57
+ @password = password
58
+ validate!
59
+ end
60
+
61
+ private
62
+
63
+ # @return [void]
64
+ # @raise [ArgumentError]
65
+ def validate!
66
+ raise ArgumentError, 'host must be specified' if host.empty?
67
+ raise ArgumentError, 'port must be positive' unless port.positive?
68
+ raise ArgumentError, 'user must be specified' if user.empty?
69
+ raise ArgumentError, 'password must be specified' if password.empty?
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MysqlAlterMonitoring
4
+ # MysqlAlterMonitoring::JsonLogger
5
+ class JsonLogger
6
+ # @return [Proc]
7
+ FORMATTER = (proc do |severity, timestamp, _progname, msg|
8
+ JSON.parse(String(msg), symbolized_name: true)
9
+ .merge(level: severity, timestamp: timestamp)
10
+ .then { "#{JSON.generate(_1)}\n" }
11
+ end).freeze
12
+ private_constant :FORMATTER
13
+
14
+ # @param io [::IO]
15
+ # @return [void]
16
+ def initialize(io)
17
+ @logger = ::Logger.new(io, formatter: FORMATTER)
18
+ end
19
+
20
+ # @param hash [Hash]
21
+ # @return [void]
22
+ def info(hash)
23
+ build_message(hash).then { logger.info(_1) }
24
+ end
25
+
26
+ # @param error [Exception]
27
+ # @param hash [Hash]
28
+ # @return [void]
29
+ def error(error, hash = {})
30
+ hash.merge(error_class: error.class.name, error_message: error.message)
31
+ .then { build_message(_1) }
32
+ .then { logger.error(_1) }
33
+ end
34
+
35
+ private
36
+
37
+ # @!attribute [r] logger
38
+ # @return [Logger]
39
+ attr_reader :logger
40
+
41
+ # @param hash [Hash]
42
+ # @return [String]
43
+ def build_message(hash)
44
+ JSON.generate(hash)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MysqlAlterMonitoring
4
+ # MysqlAlterMonitoring::Monitor
5
+ class Monitor
6
+ # @return [Integer]
7
+ EMPTY_EVENT_MAX_COUNT = 10
8
+ public_constant :EMPTY_EVENT_MAX_COUNT
9
+
10
+ # @param client [MysqlAlterMonitoring::Client]
11
+ # @param io [::IO]
12
+ # @param logging_interval [Numeric]
13
+ # @return [void]
14
+ def initialize(client, io, logging_interval: 10)
15
+ @client = client
16
+ @logger = JsonLogger.new(io)
17
+ @logging_interval = logging_interval
18
+ @performance_schema_setting = PerformanceSchemaSetting.new(client, io)
19
+ end
20
+
21
+ # Fetch ALTER TABLE events at specified intervals and output in JSON format to specified IO.
22
+ # Exit when a total of 10 empty ALTER TABLE events are fetched.
23
+ #
24
+ # @return [void]
25
+ def run_until_empty_event
26
+ empty_event_count = 0
27
+
28
+ run do |event|
29
+ empty_event_count += 1 if event.empty?
30
+ empty_event_count > EMPTY_EVENT_MAX_COUNT
31
+ end
32
+
33
+ logger.info({ message: 'Finish monitoring' })
34
+ end
35
+
36
+ # @return [void]
37
+ def run_forever
38
+ run { false }
39
+ end
40
+
41
+ private
42
+
43
+ # @!attribute [r] client
44
+ # @return [MysqlAlterMonitoring::Client]
45
+ attr_reader :client
46
+ # @!attribute [r] logger
47
+ # @return [JsonLogger]
48
+ attr_reader :logger
49
+ # @!attribute [r] logging_interval
50
+ # @return [Numeric]
51
+ attr_reader :logging_interval
52
+ # @!attribute [r] performance_schema_setting
53
+ # @return [MysqlAlterMonitoring::PerformanceSchemaSetting]
54
+ attr_reader :performance_schema_setting
55
+
56
+ # Must specify finish condition in block
57
+ #
58
+ # @yieldparam event [Hash]
59
+ # @yieldreturn [Boolean]
60
+ # @return [void]
61
+ def run # rubocop:todo Metrics/AbcSize
62
+ raise Error, 'performance schema monitoring setting is not enabled' unless performance_schema_setting.enable?
63
+
64
+ Enumerator.produce { fetch_event_stages_current }.each do |event|
65
+ raise StopIteration if yield(event)
66
+
67
+ build_log_hash(event).then { logger.info(_1) }
68
+ sleep(logging_interval)
69
+ end
70
+ rescue StandardError => e
71
+ logger.error(e)
72
+ end
73
+
74
+ # @return [Hash]
75
+ def fetch_event_stages_current
76
+ client.query('SELECT * FROM performance_schema.events_stages_current').to_a.first || {}
77
+ end
78
+
79
+ # @param event [Hash]
80
+ # f@return [Hash]
81
+ def build_log_hash(event)
82
+ return { message: 'Event is empty' } if event.empty?
83
+
84
+ { progress: calculate_progress(event), raw: event }
85
+ end
86
+
87
+ # @param event [Hash]
88
+ # @return [String]
89
+ def calculate_progress(event)
90
+ (BigDecimal(event['WORK_COMPLETED']) / BigDecimal(event['WORK_ESTIMATED']))
91
+ .then { _1 * 100 }
92
+ .then { _1.round(3) }
93
+ .then { format('%<progress>3.3f(%%)', progress: _1) }
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MysqlAlterMonitoring
4
+ # MysqlAlterMonitoring::PerformanceSchemaSetting
5
+ class PerformanceSchemaSetting
6
+ # @param client [MysqlAlterMonitoring::Client]
7
+ # @param io [::IO]
8
+ # @return [void]
9
+ def initialize(client, io)
10
+ @client = client
11
+ @logger = JsonLogger.new(io)
12
+ end
13
+
14
+ # @return [void]
15
+ def enable!
16
+ client.query <<~SQL
17
+ UPDATE performance_schema.setup_instruments SET ENABLED = 'YES'
18
+ WHERE NAME LIKE 'stage/innodb/alter%'
19
+ SQL
20
+
21
+ client.query <<~SQL
22
+ UPDATE performance_schema.setup_consumers SET ENABLED = 'YES'
23
+ WHERE NAME LIKE '%stages%'
24
+ SQL
25
+
26
+ logger.info({ message: 'Enable monitoring settings for the performance schema' })
27
+ end
28
+
29
+ # @return [Boolean]
30
+ def enable?
31
+ disabled_instruments = client.query <<~SQL
32
+ SELECT * FROM performance_schema.setup_instruments
33
+ WHERE NAME LIKE 'stage/innodb/alter%' AND ENABLED = 'NO'
34
+ SQL
35
+ disabled_consumers = client.query <<~SQL
36
+ SELECT * FROM performance_schema.setup_consumers
37
+ WHERE NAME LIKE '%stages%' AND ENABLED = 'NO'
38
+ SQL
39
+
40
+ disabled_instruments.count.zero? && disabled_consumers.count.zero?
41
+ end
42
+
43
+ # @return [void]
44
+ def disable!
45
+ client.query <<~SQL
46
+ UPDATE performance_schema.setup_instruments SET ENABLED = 'NO'
47
+ WHERE NAME LIKE 'stage/innodb/alter%'
48
+ SQL
49
+
50
+ client.query <<~SQL
51
+ UPDATE performance_schema.setup_consumers SET ENABLED = 'NO'
52
+ WHERE NAME LIKE '%stages%'
53
+ SQL
54
+
55
+ logger.info({ message: 'Disable monitoring settings for the performance schema' })
56
+ end
57
+
58
+ private
59
+
60
+ # @!attribute [r] client
61
+ # @return [MysqlAlterMonitoring::Client]
62
+ attr_reader :client
63
+ # @!attribute [r] logger
64
+ # @return [JsonLogger]
65
+ attr_reader :logger
66
+ end
67
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MysqlAlterMonitoring
4
+ # @return [String]
5
+ VERSION = '0.1.0'
6
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bigdecimal'
4
+ require 'forwardable'
5
+ require 'json'
6
+ require 'logger'
7
+ require 'uri'
8
+ require 'mysql2'
9
+ require_relative 'mysql_alter_monitoring/version'
10
+ require_relative 'mysql_alter_monitoring/config'
11
+ require_relative 'mysql_alter_monitoring/performance_schema_setting'
12
+ require_relative 'mysql_alter_monitoring/json_logger'
13
+ require_relative 'mysql_alter_monitoring/client'
14
+ require_relative 'mysql_alter_monitoring/monitor'
15
+ require_relative 'mysql_alter_monitoring/cli'
16
+
17
+ # MysqlAlterMonitoring
18
+ module MysqlAlterMonitoring
19
+ # MysqlAlterMonitoring::Error
20
+ class Error < StandardError; end
21
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/mysql_alter_monitoring/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'mysql_alter_monitoring'
7
+ spec.version = MysqlAlterMonitoring::VERSION
8
+ spec.authors = ['Akito Fujisaki']
9
+ spec.email = ['52433677+akito-fujisaki@users.noreply.github.com']
10
+
11
+ spec.summary = 'MySQL ALTER TABLE monitoring tool.'
12
+ spec.description = 'Monitoring ALTER TABLE Progress for InnoDB Tables Using Performance Schema.'
13
+ spec.homepage = 'https://github.com/akito-fujisaki/mysql_alter_monitoring'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 2.6.0'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = spec.homepage
19
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(__dir__) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
26
+ end
27
+ end
28
+ spec.bindir = 'exe'
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ['lib']
31
+
32
+ spec.add_dependency 'mysql2'
33
+ spec.metadata['rubygems_mfa_required'] = 'true'
34
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mysql_alter_monitoring
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Akito Fujisaki
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-02-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mysql2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Monitoring ALTER TABLE Progress for InnoDB Tables Using Performance Schema.
28
+ email:
29
+ - 52433677+akito-fujisaki@users.noreply.github.com
30
+ executables:
31
+ - mysql-alter-monitoring
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".rspec"
36
+ - ".rubocop.yml"
37
+ - CHANGELOG.md
38
+ - CODE_OF_CONDUCT.md
39
+ - Dockerfile
40
+ - Gemfile
41
+ - Gemfile.lock
42
+ - LICENSE.txt
43
+ - README.md
44
+ - Rakefile
45
+ - compose.yml
46
+ - exe/mysql-alter-monitoring
47
+ - lib/mysql_alter_monitoring.rb
48
+ - lib/mysql_alter_monitoring/cli.rb
49
+ - lib/mysql_alter_monitoring/client.rb
50
+ - lib/mysql_alter_monitoring/config.rb
51
+ - lib/mysql_alter_monitoring/json_logger.rb
52
+ - lib/mysql_alter_monitoring/monitor.rb
53
+ - lib/mysql_alter_monitoring/performance_schema_setting.rb
54
+ - lib/mysql_alter_monitoring/version.rb
55
+ - mysql_alter_monitoring.gemspec
56
+ homepage: https://github.com/akito-fujisaki/mysql_alter_monitoring
57
+ licenses:
58
+ - MIT
59
+ metadata:
60
+ homepage_uri: https://github.com/akito-fujisaki/mysql_alter_monitoring
61
+ source_code_uri: https://github.com/akito-fujisaki/mysql_alter_monitoring
62
+ changelog_uri: https://github.com/akito-fujisaki/mysql_alter_monitoring/blob/main/CHANGELOG.md
63
+ rubygems_mfa_required: 'true'
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 2.6.0
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubygems_version: 3.4.1
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: MySQL ALTER TABLE monitoring tool.
83
+ test_files: []