zuazo/mysql_tuning-cookbook

View on GitHub
libraries/mysql_helpers.rb

Summary

Maintainability
A
0 mins
Test Coverage
# encoding: UTF-8
#
# Cookbook Name:: mysql_tuning
# Library:: mysql_helpers
# Author:: Xabier de Zuazo (<xabier@zuazo.org>)
# Copyright:: Copyright (c) 2015 Xabier de Zuazo
# Copyright:: Copyright (c) 2014 Onddo Labs, SL.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

class MysqlTuningCookbook
  # Some generic helpers related with MySQL
  class MysqlHelpers
    def self.numeric?(num)
      case num
      when Numeric then true
      when /^[0-9]+[GMKB]?$/ then true
      else
        false
      end
    end

    def self.mysql2num(num)
      return num.to_i unless num =~ /^([0-9]+)([GMKB])$/
      base = case Regexp.last_match[2]
             when 'G' then 1_073_741_824
             when 'M' then 1_048_576
             when 'K' then 1024
             when 'B' then 1
             end
      Regexp.last_match[1].to_i * base
    end

    def self.num2mysql(num)
      if num > 10_737_418_240
        "#{(num / 1_073_741_824).floor}G"
      elsif num > 10_485_760
        "#{(num / 1_048_576).floor}M"
      elsif num > 10_240
        "#{(num / 1024).floor}K"
      else
        num.to_s
      end
    end

    # Returns true if all variables has been set correctly
    def self.set_variables(vars, user, password, port)
      db = connect(user, password, port.to_i)

      result = vars.reduce(true) do |r, (key, value)|
        return false unless r
        set_variable(db, key, value)
      end

      disconnect(db)
      result
    end

    # private

    def self.connect(user, password, port)
      require 'mysql2'
      # TODO: use the socket?
      Mysql2::Client.new(
        host: '127.0.0.1',
        username: user,
        password: password,
        port: port
      )
    end
    private_class_method :connect

    def self.disconnect(db)
      db.close
    rescue
      nil
    end
    private_class_method :disconnect

    def self.variable_exists?(db, name)
      value = db.query("SHOW GLOBAL VARIABLES LIKE '#{db.escape(name)}'")
      value.count > 0
    end
    private_class_method :variable_exists?

    def self.get_variable(db, name)
      value = db.query("SHOW GLOBAL VARIABLES LIKE '#{db.escape(name)}'")
      value.count > 0 ? value.first.values[1] : nil
    end
    private_class_method :get_variable

    def self.set_variable_query(db, name, value)
      value = mysql2num(value) if numeric?(value)
      db.query("SET GLOBAL #{name} = #{value}")
      Chef::Log.info("Changed MySQL #{name.inspect} variable "\
        "to #{value.inspect} dynamically")
      true
    rescue Mysql2::Error => e
      Chef::Log.info("MySQL #{name.inspect} variable cannot be changed "\
        "to #{value.inspect} dynamically: #{e.message}")
      false
    end
    private_class_method :set_variable_query

    def self.set_variable(db, name, value)
      return false unless variable_exists?(db, name)
      return true if get_variable(db, name).to_s == value.to_s
      set_variable_query(db, name, value)
    end
    private_class_method :set_variable
  end
end