module CartoDB module Stats class APICalls # REDIS_SOURCES # mapviews -> mapviews sent from tiler to redis # mapviews_es -> mapviews sent from logs to redis REDIS_SOURCES = ['mapviews', 'mapviews_es'] # This method will return an array without dates def get_api_calls_without_dates(username, options = {}) calls = get_api_calls(username, options).map {|day, value| value} if options[:old_api_calls] raise "Cannot request old api calls with custom dates" if options[:to] or options[:from] # Add old api calls old_calls = get_old_api_calls(username) rescue [] calls = calls.zip(old_calls).map { |pair| pair[0].to_i + pair[1].to_i } unless old_calls.blank? end return calls end # This method will never include old api calls # It will return a hash of dates with map views values def get_api_calls_with_dates(username, options = {}) get_api_calls(username, options) end # Wrapper to get a total of api calls of a user or visualization # It doesn't include old api calls def get_total_api_calls(username, visualization_id = nil) get_total_api_calls_from_redis(username, visualization_id) end def get_api_calls(username, options = {}) get_api_calls_from_redis(username, options) end # DEPRECATED # This method returns a 30 days ordered array # This array is populated from other tasks. It's not possible # to pass a days parameter to this method def get_old_api_calls(username) user_redis_key = ::User.where(:username => username).first.key calls = $users_metadata.HMGET(user_redis_key, 'api_calls').first if calls.nil? || calls['per_day'].nil? return [] else JSON.parse(calls['per_day']).to_a.reverse end end # Iterate through all api calls redis sources and returns total # api calls of a user or a visualization def get_total_api_calls_from_redis(username, visualization_id = nil) calls = 0 REDIS_SOURCES.each do |source| source_calls = get_total_api_calls_from_redis_source(username, source, visualization_id) if !source_calls.nil? and source_calls != 0 calls = calls + source_calls end end return calls end # Iterate through all api calls redis sources and returns total # api calls per day def get_api_calls_from_redis(username, options = {}) calls = {} REDIS_SOURCES.each do |source| source_calls = get_api_calls_from_redis_source(username, source, options) if calls.blank? calls = source_calls else if !source_calls.blank? source_calls.each do |day, value| if !value.nil? calls[day] = (calls[day].nil? ? 0 : calls[day]) + value.to_i end end end end end return calls end # Get redis key based on username and visualization id def redis_api_call_key(username, api_call_type, visualization_id = nil) redis_base_key = "user:#{username}:#{api_call_type}" if visualization_id.nil? return "#{redis_base_key}:global" else return "#{redis_base_key}:stat_tag:#{visualization_id}" end end # Returns api calls from a redis key in a hash with dates def get_api_calls_from_redis_source(username, api_call_type, options = {}) redis_key = redis_api_call_key(username, api_call_type, options[:stat_tag]) date_to = (options[:to] ? options[:to].to_date : Date.today) date_from = (options[:from] ? options[:from].to_date : date_to - 29.days) # Retrieve matching months from Redis with ZSCAN # TODO: move to single request if Redis gem ever supports multiple matches calls = [] matching_months_date = date_from.beginning_of_month while matching_months_date <= date_to $users_metadata.zscan_each(redis_key, match: "#{matching_months_date.strftime('%Y%m')}*") do |key| calls.push(key) end matching_months_date += 1.month end # Take only the date, score tuples (arrays), flatten (to remove array levels), group again date,score pairs and sort it by date desc calls_in_reverse_order = calls.select { |a| a.kind_of? Array }.flatten.each_slice(2).to_a.sort { |a1, a2| a1[0] <=> a2[0]}.reverse # Crop requested window last_requested_day = date_to.strftime("%Y%m%d") first_requested_day = date_from.strftime("%Y%m%d") if check_available_values(calls_in_reverse_order, first_requested_day, last_requested_day) first_requested_index = calls_in_reverse_order.index { |day_and_count| day_and_count[0] <= last_requested_day } calls_in_reverse_order = calls_in_reverse_order.drop(first_requested_index) if first_requested_index last_requested_index = calls_in_reverse_order.index { |day_and_count| day_and_count[0] <= first_requested_day } calls_in_reverse_order = calls_in_reverse_order.take(last_requested_index + 1) if last_requested_index else calls_in_reverse_order = {} end # Fill gaps whole_range_zero = {} date_to.downto(date_from) do |date| stat_date = date.strftime("%Y%m%d") whole_range_zero[stat_date] = 0 end whole_range_zero.merge(Hash[*calls_in_reverse_order.flatten]) end def check_available_values(values, date_from, date_to) values.select { |value| value[0] >= date_from && value[0] <= date_to }.length > 0 end # Returns total api calls from a redis key def get_total_api_calls_from_redis_source(username, api_call_type, visualization_id = nil) raise "Total api calls per user is not supported yet" if visualization_id.nil? redis_key = redis_api_call_key(username, api_call_type, visualization_id) return $users_metadata.ZSCORE(redis_key, 'total').to_i end end end end