<commit_before>module Jetpants
  
  #--
  # State accessors ############################################################
  #++
  
  class DB
    # Returns the Jetpants::DB instance that is the master of this instance, or false if
    # there isn't one, or nil if we can't tell because this instance isn't running.
    def master
      return nil unless running? || @master
      probe if @master.nil?
      @master
    end
    
    # Returns an Array of Jetpants::DB instances that are slaving from this instance,
    # or nil if we can't tell because this instance isn't running.
    def slaves
      return nil unless running? || @slaves
      probe if @slaves.nil?
      @slaves
    end
    
    # Returns true if replication is paused on this instance, false if it isn't, or
    # nil if this instance isn't a slave (or if we can't tell because the instance
    # isn't running)
    def repl_paused?
      return nil unless master
      probe if @repl_paused.nil?
      @repl_paused
    end

    # Returns true if MySQL is running for this instance, false otherwise.
    # Note that if the host isn't available/online/reachable, we consider
    # MySQL to not be running.
    def running?
      probe if @running.nil?
      @running
    end
    
    # Returns true if we've probed this MySQL instance already.  Several
    # methods trigger a probe, including master, slaves, repl_paused?, and
    # running?.
    def probed?
      [@master, @slaves, @running].compact.count >= 3
    end

    # Probes this instance to discover its status, master, and slaves. Several
    # other methods trigger a probe automatically, including master, slaves,
    # repl_paused?, and running?.
    # Ordinarily this method won't re-probe an instance that has already been
    # probed, unless you pass force=true.  This can be useful if something
    # external to Jetpants has changed a DB's state while Jetpants is running.
    # For example, if you're using jetpants console and, for whatever reason,
    # you stop replication on a slave manually outside of Jetpants.  In this
    # case you will need to force a probe so that Jetpants learns about the
    # change.
    def probe(force=false)
      return if probed? && !force
      output "Probing MySQL installation"
      probe_running
      probe_master
      probe_slaves
    end
    
    # Alias for probe(true)
    def probe!() probe(true) end
    
    # Returns true if the MySQL slave I/O thread and slave SQL thread are
    # both running, false otherwise.  Note that this always checks the current
    # actual state of the instance, as opposed to DB#repl_paused? which just
    # remembers the state from the previous probe and any actions since then.
    def replicating?
      status = slave_status
      [status[:slave_io_running], status[:slave_sql_running]].all? {|s| s.downcase == 'yes'}
    end

    # Returns true if this instance has a master, false otherwise.
    def is_slave?
      !!master
    end

    # Returns true if this instance had at least one slave when it was last
    # probed, false otherwise. (This method will indirectly force a probe if
    # the instance hasn't been probed before.)
    def has_slaves?
      slaves.count > 0
    end

    # Returns true if the global READ_ONLY variable is set, false otherwise.
    def read_only?
      global_variables[:read_only].downcase == 'on'
    end

    # Confirms instance has no more than [max] connections currently
    # (AS VISIBLE TO THE APP USER), and in [interval] seconds hasn't
    # received more than [threshold] additional connections.
    # You may need to adjust max if running multiple query killers,
    # monitoring agents, etc.
    def taking_connections?(max=4, interval=2.0, threshold=1)
      current_conns = query_return_array('show processlist').count
      return true if current_conns > max
      conn_counter = global_status[:Connections].to_i
      sleep(interval)
      global_status[:Connections].to_i - conn_counter > threshold
    end

    # Returns true if this instance appears to be a standby slave,
    # false otherwise. Note that "standby" in this case is based
    # on whether the slave is actively receiving connections, not
    # based on any Pool's understanding of the slave's state. An asset-
    # tracker plugin may want to override this to determine standby
    # status differently.
    def is_standby?
      !(running?) || (is_slave? && !taking_connections?)
    end
    
    # Jetpants supports a notion of dedicated backup machines, containing one
    # or more MySQL instances that are considered "backup slaves", which will
    # never be promoted to serve production queries.  The default
    # implementation identifies these by a hostname beginning with "backup".
    # You may want to override this with a plugin to use a different scheme
    # if your architecture contains a similar type of node.
    def for_backups?
      @host.hostname.start_with? 'backup'
    end
    
    # Returns a hash mapping global MySQL variables (as symbols)
    # to their values (as strings).
    def global_variables
      query_return_array('show global variables').reduce do |variables, variable|
        variables[variable[:Variable_name].to_sym] = variable[:Value]
        variables
      end
    end
    
    # Returns a hash mapping global MySQL status fields (as symbols)
    # to their values (as strings).
    def global_status
      query_return_array('show global status').reduce do |variables, variable|
        variables[variable[:Variable_name].to_sym] = variable[:Value]
        variables      
      end
    end

    # Returns the Jetpants::Pool that this instance belongs to, if any.
    def pool
      Jetpants.topology.pool(self) || Jetpants.topology.pool(master)
    end
    
    
    ###### Private methods #####################################################
    
    private<commit_msg>Add new DB#role method, to figure out what type of node a given DB instance is<commit_after>module Jetpants
  
  #--
  # State accessors ############################################################
  #++
  
  class DB
    # Returns the Jetpants::DB instance that is the master of this instance, or false if
    # there isn't one, or nil if we can't tell because this instance isn't running.
    def master
      return nil unless running? || @master
      probe if @master.nil?
      @master
    end
    
    # Returns an Array of Jetpants::DB instances that are slaving from this instance,
    # or nil if we can't tell because this instance isn't running.
    def slaves
      return nil unless running? || @slaves
      probe if @slaves.nil?
      @slaves
    end
    
    # Returns true if replication is paused on this instance, false if it isn't, or
    # nil if this instance isn't a slave (or if we can't tell because the instance
    # isn't running)
    def repl_paused?
      return nil unless master
      probe if @repl_paused.nil?
      @repl_paused
    end

    # Returns true if MySQL is running for this instance, false otherwise.
    # Note that if the host isn't available/online/reachable, we consider
    # MySQL to not be running.
    def running?
      probe if @running.nil?
      @running
    end
    
    # Returns true if we've probed this MySQL instance already.  Several
    # methods trigger a probe, including master, slaves, repl_paused?, and
    # running?.
    def probed?
      [@master, @slaves, @running].compact.count >= 3
    end

    # Probes this instance to discover its status, master, and slaves. Several
    # other methods trigger a probe automatically, including master, slaves,
    # repl_paused?, and running?.
    # Ordinarily this method won't re-probe an instance that has already been
    # probed, unless you pass force=true.  This can be useful if something
    # external to Jetpants has changed a DB's state while Jetpants is running.
    # For example, if you're using jetpants console and, for whatever reason,
    # you stop replication on a slave manually outside of Jetpants.  In this
    # case you will need to force a probe so that Jetpants learns about the
    # change.
    def probe(force=false)
      return if probed? && !force
      output "Probing MySQL installation"
      probe_running
      probe_master
      probe_slaves
    end
    
    # Alias for probe(true)
    def probe!() probe(true) end
    
    # Returns true if the MySQL slave I/O thread and slave SQL thread are
    # both running, false otherwise.  Note that this always checks the current
    # actual state of the instance, as opposed to DB#repl_paused? which just
    # remembers the state from the previous probe and any actions since then.
    def replicating?
      status = slave_status
      [status[:slave_io_running], status[:slave_sql_running]].all? {|s| s.downcase == 'yes'}
    end

    # Returns true if this instance has a master, false otherwise.
    def is_slave?
      !!master
    end

    # Returns true if this instance had at least one slave when it was last
    # probed, false otherwise. (This method will indirectly force a probe if
    # the instance hasn't been probed before.)
    def has_slaves?
      slaves.count > 0
    end

    # Returns true if the global READ_ONLY variable is set, false otherwise.
    def read_only?
      global_variables[:read_only].downcase == 'on'
    end

    # Confirms instance has no more than [max] connections currently
    # (AS VISIBLE TO THE APP USER), and in [interval] seconds hasn't
    # received more than [threshold] additional connections.
    # You may need to adjust max if running multiple query killers,
    # monitoring agents, etc.
    def taking_connections?(max=4, interval=2.0, threshold=1)
      current_conns = query_return_array('show processlist').count
      return true if current_conns > max
      conn_counter = global_status[:Connections].to_i
      sleep(interval)
      global_status[:Connections].to_i - conn_counter > threshold
    end

    # Returns true if this instance appears to be a standby slave,
    # false otherwise. Note that "standby" in this case is based
    # on whether the slave is actively receiving connections, not
    # based on any Pool's understanding of the slave's state. An asset-
    # tracker plugin may want to override this to determine standby
    # status differently.
    def is_standby?
      !(running?) || (is_slave? && !taking_connections?)
    end
    
    # Jetpants supports a notion of dedicated backup machines, containing one
    # or more MySQL instances that are considered "backup slaves", which will
    # never be promoted to serve production queries.  The default
    # implementation identifies these by a hostname beginning with "backup".
    # You may want to override this with a plugin to use a different scheme
    # if your architecture contains a similar type of node.
    def for_backups?
      @host.hostname.start_with? 'backup'
    end
    
    # Returns a hash mapping global MySQL variables (as symbols)
    # to their values (as strings).
    def global_variables
      query_return_array('show global variables').reduce do |variables, variable|
        variables[variable[:Variable_name].to_sym] = variable[:Value]
        variables
      end
    end
    
    # Returns a hash mapping global MySQL status fields (as symbols)
    # to their values (as strings).
    def global_status
      query_return_array('show global status').reduce do |variables, variable|
        variables[variable[:Variable_name].to_sym] = variable[:Value]
        variables      
      end
    end

    # Returns the Jetpants::Pool that this instance belongs to, if any.
    def pool
      Jetpants.topology.pool(self) || Jetpants.topology.pool(master)
    end
    
    # Determines the DB's role in its pool. Returns either :master,
    # :active_slave, :standby_slave, or :backup_slave.
    #
    # Note that we consider a node with no master and no slaves to be
    # a :master, since we can't determine if it had slaves but they're
    # just offline/dead, vs it being an orphaned machine.
    def role
      p = pool
      case
      when !@master && has_slaves? then :master
      when for_backups? then :backup_slave
      when p && p.active_slave_weights[self] then :active_slave # if pool in topology, determine based on expected/ideal state
      when !p && !is_standby? then :active_slave                # if pool missing from topology, determine based on actual state
      else :standby_slave
      end
    end
    
    ###### Private methods #####################################################
    
    private
{'fraction_non_alphanumeric': 0.08298227485891424, 'fraction_numerical': 0.0009538192512518877, 'mean_word_length': 2.8977695167286246, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2816448', 'n_tokens_mistral': 3425, 'n_tokens_neox': 3284, 'n_words': 1467}