79 lines
		
	
	
		
			2.1 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			79 lines
		
	
	
		
			2.1 KiB
		
	
	
	
		
			Ruby
		
	
	
	
| # frozen_string_literal: true
 | |
| 
 | |
| module Gitlab
 | |
|   module Database
 | |
|     class StatActivity
 | |
|       # Keep 5 minutes worth of samples
 | |
|       SAMPLING_WINDOW_SECONDS = 60 * 5
 | |
| 
 | |
|       attr_reader :connection_name
 | |
| 
 | |
|       class << self
 | |
|         def write(connection_name, sample)
 | |
|           new(connection_name).write(sample)
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def initialize(connection_name)
 | |
|         @connection_name = connection_name
 | |
|       end
 | |
| 
 | |
|       def write(sample)
 | |
|         aggregate_sample_data(sample).each do |application, database, payload|
 | |
|           update_cached_samples(application, database, payload)
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       private
 | |
| 
 | |
|       def hash_key(application)
 | |
|         "gitlab:pg_stat_sampler:#{connection_name}:#{application}:samples"
 | |
|       end
 | |
| 
 | |
|       def update_cached_samples(application, database, payload)
 | |
|         cached_samples = with_redis do |c|
 | |
|           c.hget(hash_key(application), database)
 | |
|         end
 | |
| 
 | |
|         now = Time.now.utc.to_i
 | |
|         sample = {
 | |
|           'created_at' => now,
 | |
|           'payload' => payload
 | |
|         }
 | |
| 
 | |
|         existing_samples = cached_samples ? ::Gitlab::Json.parse(cached_samples) : []
 | |
|         existing_samples.append(sample)
 | |
| 
 | |
|         existing_samples = existing_samples.filter { |s| s['created_at'] > now - SAMPLING_WINDOW_SECONDS }
 | |
| 
 | |
|         with_redis do |c|
 | |
|           c.hset(hash_key(application), database, ::Gitlab::Json.dump(existing_samples))
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def aggregate_sample_data(data)
 | |
|         data
 | |
|           .filter { |d| d['application'] && d['endpoint'] && d['database'] && d['state'] }
 | |
|           .group_by { |c| [c['application'], c['database']] }
 | |
|           .map do |group_on, inner|
 | |
|             application = group_on[0]
 | |
|             db_config_database = group_on[1]
 | |
| 
 | |
|             # get a map of { endpoints -> { state -> count } }
 | |
|             payload = inner
 | |
|               .group_by { |c| c['endpoint'] }
 | |
|               .transform_values do |value|
 | |
|               value.to_h { |tup| [tup['state'], tup['count']] }
 | |
|             end
 | |
| 
 | |
|             [application, db_config_database, payload]
 | |
|           end
 | |
|       end
 | |
| 
 | |
|       def with_redis(&)
 | |
|         Gitlab::Redis::SharedState.with(&)
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 |