<commit_before>#!/usr/bin/env ruby

require 'oj'

require 'flapjack/patches'

require 'flapjack/data/contact'
require 'flapjack/data/event'
require 'flapjack/data/entity'

# TODO might want to split the class methods out to a separate class, DAO pattern
# ( http://en.wikipedia.org/wiki/Data_access_object ).

module Flapjack

  module Data

    class EntityCheck

      STATE_OK              = 'ok'
      STATE_WARNING         = 'warning'
      STATE_CRITICAL        = 'critical'
      STATE_UNKNOWN         = 'unknown'

      NOTIFICATION_STATES = [:problem, :warning, :critical, :unknown,
                             :recovery, :acknowledgement]

      attr_accessor :entity, :check

      # TODO probably shouldn't always be creating on query -- work out when this should be happening
      def self.for_event_id(event_id, options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        logger = options[:logger]
        entity_name, check = event_id.split(':', 2)
        self.new(Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis, :create => true, :logger => true), check,
          :redis => redis, :logger => logger)
      end

      # TODO probably shouldn't always be creating on query -- work out when this should be happening
      def self.for_entity_name(entity_name, check, options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        self.new(Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis, :create => true), check,
          :redis => redis)
      end

      def self.for_entity_id(entity_id, check, options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        self.new(Flapjack::Data::Entity.find_by_id(entity_id, :redis => redis), check,
          :redis => redis)
      end

      def self.for_entity(entity, check, options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        self.new(entity, check, :redis => redis)
      end

      def self.find_all_for_entity_name(entity_name, options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        redis.zrange("current_checks:#{entity_name}", 0, -1)
      end

      def self.find_all(options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        self.conflate_to_keys(self.find_all_by_entity(:redis => redis))
      end

      def self.find_all_by_entity(options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        d = {}
        redis.zrange("current_entities", 0, -1).each {|entity|
          d[entity] = redis.zrange("current_checks:#{entity}", 0, -1)
        }
        d
      end

      def self.count_all(options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        redis.zrange("current_entities", 0, -1).inject(0) {|memo, entity|
          memo + redis.zcount("current_checks:#{entity}", '-inf', '+inf')
        }
      end

      def self.find_all_failing(options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        self.conflate_to_keys(self.find_all_failing_by_entity(:redis => redis))
      end

      def self.find_all_failing_by_entity(options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        redis.zrange("failed_checks", 0, -1).inject({}) do |memo, key|
          entity, check = key.split(':', 2)
          if !!redis.zscore("current_checks:#{entity}", check)
            memo[entity] ||= []
            memo[entity] << check
          end
          memo
        end
      end

      def self.count_all_failing(options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        redis.zrange("failed_checks", 0, -1).count do |key|
          entity, check = key.split(':', 2)
          !!redis.zscore("current_checks:#{entity}", check)
        end
      end

      def self.conflate_to_keys(entity_checks_hash)
        result = []
        entity_checks_hash.each {|entity, checks|
          checks.each {|check|
            result << "#{entity}:#{check}"
          }
        }
        result
      end

      def self.in_unscheduled_maintenance_for_event_id?(event_id, options)
        raise "Redis connection not set" unless redis = options[:redis]
        redis.exists("#{event_id}:unscheduled_maintenance")
      end

      def self.in_scheduled_maintenance_for_event_id?(event_id, options)
        raise "Redis connection not set" unless redis = options[:redis]
        redis.exists("#{event_id}:scheduled_maintenance")
      end

      def self.state_for_event_id?(event_id, options)
        raise "Redis connection not set" unless redis = options[:redis]
        redis.hget("check:#{event_id}", 'state')
      end

      # takes an array of ages (in seconds) to split all checks up by
      # - age means how long since the last update
      # - 0 age is implied if not explicitly passed
      # returns arrays of all current checks hashed by age range upper bound, eg:
      #
      # EntityCheck.find_all_split_by_freshness([60, 300], opts) =>
      #   {   0 => [ 'foo-app-01:SSH' ],
      #      60 => [ 'foo-app-01:Ping', 'foo-app-01:Disk / Utilisation' ],
      #     300 => [] }
      #
      # you can also set :counts to true in options and you'll just get the counts, eg:
      #
      # EntityCheck.find_all_split_by_freshness([60, 300], opts.merge(:counts => true)) =>
      #   {   0 => 1,
      #      60 => 3,
      #     300 => 0 }
      #
      # and you can get the last update time with each check too by passing :with_times => true eg:
      #
      # EntityCheck.find_all_split_by_freshness([60, 300], opts.merge(:with_times => true)) =>
      #   {   0 => [ ['foo-app-01:SSH', 1382329923.0] ],
      #      60 => [ ['foo-app-01:Ping', 1382329922.0], ['foo-app-01:Disk / Utilisation', 1382329921.0] ],
      #     300 => [] }
      #
      def self.find_all_split_by_freshness(ages, options)
        raise "Redis connection not set" unless redis = options[:redis]
        raise "ages must be an array" unless ages.is_a?(Array)
        ages.each do |age|
          raise "ages must each be an integer" unless age.is_a?(Integer)
        end
        ages << 0
        ages = ages.sort.uniq

        start_time = Time.now

        checks = []
        # get all the current checks, with last update time
        Flapjack::Data::Entity.find_all_current(:redis => redis).each do |entity|
          redis.zrange("current_checks:#{entity}", 0, -1, {:withscores => true}).each do |check|
            check[0] = "#{entity}:#{check[0]}"
            checks << check
          end
        end

        results_with_times = {}
        ages_length = ages.length
        ages.each_with_index do |age, index|
          results_with_times[age] = checks.select do |check|
            check_age = start_time.to_i - check[1]
            check_age = 0 unless check_age > 0
            if ages.length == (index + 1)
              check_age >= age
            else
              (check_age >= age) && (check_age < ages[index + 1])
            end<commit_msg>use responds_to? instead of is_a?<commit_after>#!/usr/bin/env ruby

require 'oj'

require 'flapjack/patches'

require 'flapjack/data/contact'
require 'flapjack/data/event'
require 'flapjack/data/entity'

# TODO might want to split the class methods out to a separate class, DAO pattern
# ( http://en.wikipedia.org/wiki/Data_access_object ).

module Flapjack

  module Data

    class EntityCheck

      STATE_OK              = 'ok'
      STATE_WARNING         = 'warning'
      STATE_CRITICAL        = 'critical'
      STATE_UNKNOWN         = 'unknown'

      NOTIFICATION_STATES = [:problem, :warning, :critical, :unknown,
                             :recovery, :acknowledgement]

      attr_accessor :entity, :check

      # TODO probably shouldn't always be creating on query -- work out when this should be happening
      def self.for_event_id(event_id, options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        logger = options[:logger]
        entity_name, check = event_id.split(':', 2)
        self.new(Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis, :create => true, :logger => true), check,
          :redis => redis, :logger => logger)
      end

      # TODO probably shouldn't always be creating on query -- work out when this should be happening
      def self.for_entity_name(entity_name, check, options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        self.new(Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis, :create => true), check,
          :redis => redis)
      end

      def self.for_entity_id(entity_id, check, options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        self.new(Flapjack::Data::Entity.find_by_id(entity_id, :redis => redis), check,
          :redis => redis)
      end

      def self.for_entity(entity, check, options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        self.new(entity, check, :redis => redis)
      end

      def self.find_all_for_entity_name(entity_name, options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        redis.zrange("current_checks:#{entity_name}", 0, -1)
      end

      def self.find_all(options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        self.conflate_to_keys(self.find_all_by_entity(:redis => redis))
      end

      def self.find_all_by_entity(options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        d = {}
        redis.zrange("current_entities", 0, -1).each {|entity|
          d[entity] = redis.zrange("current_checks:#{entity}", 0, -1)
        }
        d
      end

      def self.count_all(options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        redis.zrange("current_entities", 0, -1).inject(0) {|memo, entity|
          memo + redis.zcount("current_checks:#{entity}", '-inf', '+inf')
        }
      end

      def self.find_all_failing(options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        self.conflate_to_keys(self.find_all_failing_by_entity(:redis => redis))
      end

      def self.find_all_failing_by_entity(options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        redis.zrange("failed_checks", 0, -1).inject({}) do |memo, key|
          entity, check = key.split(':', 2)
          if !!redis.zscore("current_checks:#{entity}", check)
            memo[entity] ||= []
            memo[entity] << check
          end
          memo
        end
      end

      def self.count_all_failing(options = {})
        raise "Redis connection not set" unless redis = options[:redis]
        redis.zrange("failed_checks", 0, -1).count do |key|
          entity, check = key.split(':', 2)
          !!redis.zscore("current_checks:#{entity}", check)
        end
      end

      def self.conflate_to_keys(entity_checks_hash)
        result = []
        entity_checks_hash.each {|entity, checks|
          checks.each {|check|
            result << "#{entity}:#{check}"
          }
        }
        result
      end

      def self.in_unscheduled_maintenance_for_event_id?(event_id, options)
        raise "Redis connection not set" unless redis = options[:redis]
        redis.exists("#{event_id}:unscheduled_maintenance")
      end

      def self.in_scheduled_maintenance_for_event_id?(event_id, options)
        raise "Redis connection not set" unless redis = options[:redis]
        redis.exists("#{event_id}:scheduled_maintenance")
      end

      def self.state_for_event_id?(event_id, options)
        raise "Redis connection not set" unless redis = options[:redis]
        redis.hget("check:#{event_id}", 'state')
      end

      # takes an array of ages (in seconds) to split all checks up by
      # - age means how long since the last update
      # - 0 age is implied if not explicitly passed
      # returns arrays of all current checks hashed by age range upper bound, eg:
      #
      # EntityCheck.find_all_split_by_freshness([60, 300], opts) =>
      #   {   0 => [ 'foo-app-01:SSH' ],
      #      60 => [ 'foo-app-01:Ping', 'foo-app-01:Disk / Utilisation' ],
      #     300 => [] }
      #
      # you can also set :counts to true in options and you'll just get the counts, eg:
      #
      # EntityCheck.find_all_split_by_freshness([60, 300], opts.merge(:counts => true)) =>
      #   {   0 => 1,
      #      60 => 3,
      #     300 => 0 }
      #
      # and you can get the last update time with each check too by passing :with_times => true eg:
      #
      # EntityCheck.find_all_split_by_freshness([60, 300], opts.merge(:with_times => true)) =>
      #   {   0 => [ ['foo-app-01:SSH', 1382329923.0] ],
      #      60 => [ ['foo-app-01:Ping', 1382329922.0], ['foo-app-01:Disk / Utilisation', 1382329921.0] ],
      #     300 => [] }
      #
      def self.find_all_split_by_freshness(ages, options)
        raise "Redis connection not set" unless redis = options[:redis]

        raise "ages does not respond_to? :each and :each_with_index" unless ages.respond_to?(:each) && ages.respond_to?(:each_with_index)
        raise "age values must respond_to? :to_i" unless ages.all? {|age| age.respond_to?(:to_i) }

        ages << 0
        ages = ages.sort.uniq

        start_time = Time.now

        checks = []
        # get all the current checks, with last update time
        Flapjack::Data::Entity.find_all_current(:redis => redis).each do |entity|
          redis.zrange("current_checks:#{entity}", 0, -1, {:withscores => true}).each do |check|
            check[0] = "#{entity}:#{check[0]}"
            checks << check
          end
        end

        results_with_times = {}
        ages_length = ages.length
        ages.each_with_index do |age, index|
          results_with_times[age] = checks.select do |check|
            check_age = start_time.to_i - check[1]
            check_age = 0 unless check_age > 0
            if ages.length == (index + 1)
              check_age >= age
            else
              (check_age >= age) && (check_age < ages[index + 1])
            end
{'fraction_non_alphanumeric': 0.12240490455622126, 'fraction_numerical': 0.015048070224327713, 'mean_word_length': 2.408119658119658, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 69, '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': '1536264', 'n_tokens_mistral': 4657, 'n_tokens_neox': 4459, 'n_words': 1127}