Class: RequestResponseStats::RequestResponse

Inherits:
Object
  • Object
show all
Defined in:
lib/request_response_stats/request_response.rb

Constant Summary collapse

LONGEST_REQ_RES_CYCLE =
2.hours
SECONDS_PRECISION =
3
MEMORY_PRECISION =
0
SYS_CALL_FREQ =
60.seconds
GROUP_STATS_BY_TIME_DURATION =

Set ‘GROUP_STATS_BY_TIME_DURATION` to `false` if no time based grouping is required, otherwise you can set it to value such as `1.minute` (but within a day)

1.minute

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(req = nil, res = nil, opts = {redis_connection: $redis, mongoid_doc_model: ReqResStat, gather_stats: true}) ⇒ RequestResponse

Here: ‘redis_connection` is connection to redis db `mongoid_doc_model` is Mongoid::Document model which specifies document schema compatible to data structure in redis if `gather_stats` is `false`, they new data won’t be added to the redis db



24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/request_response_stats/request_response.rb', line 24

def initialize(req=nil, res=nil, opts={redis_connection: $redis, mongoid_doc_model: ReqResStat, gather_stats: true})
  @request = req
  @response = res
  @redis = opts[:redis_connection]
  @mongoid_doc_model = opts[:mongoid_doc_model]
  @gather_stats = opts[:gather_stats]

  @redis_record = RedisRecord

  # adding behavior to dependents
  temp_redis = @redis  # TODO: check why using @redis directly is not working. Do instance variable have specifal meaning inside defin_singleton_method block?
  @redis_record.define_singleton_method(:redis) { temp_redis }
  @redis_record.define_singleton_method(:group_stats_by_time_duration) { GROUP_STATS_BY_TIME_DURATION }
end

Instance Attribute Details

#gather_statsObject

Returns the value of attribute gather_stats.



10
11
12
# File 'lib/request_response_stats/request_response.rb', line 10

def gather_stats
  @gather_stats
end

#mongoid_doc_modelObject

Returns the value of attribute mongoid_doc_model.



10
11
12
# File 'lib/request_response_stats/request_response.rb', line 10

def mongoid_doc_model
  @mongoid_doc_model
end

#redisObject

Returns the value of attribute redis.



10
11
12
# File 'lib/request_response_stats/request_response.rb', line 10

def redis
  @redis
end

#redis_recordObject

Returns the value of attribute redis_record.



9
10
11
# File 'lib/request_response_stats/request_response.rb', line 9

def redis_record
  @redis_record
end

#requestObject

Returns the value of attribute request.



8
9
10
# File 'lib/request_response_stats/request_response.rb', line 8

def request
  @request
end

#responseObject

Returns the value of attribute response.



8
9
10
# File 'lib/request_response_stats/request_response.rb', line 8

def response
  @response
end

Instance Method Details

#capture_request_response_cycle_end_info(capture_error: false) ⇒ Object

captures respose info and makes use of already captured request info to save info about current request-response cycle to redis



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/request_response_stats/request_response.rb', line 70

def capture_request_response_cycle_end_info(capture_error: false)
  return gather_stats unless gather_stats

  # get system info
  current_time = get_system_current_time
  current_used_memory = get_system_used_memory_mb
  current_swap_memory = get_system_used_swap_memory_mb
  current_hostname = get_server_hostname
  current_gc_stat = get_gc_stat

  res_info = {
    req_object_id: request.object_id,
    res_object_id: response.object_id,
    res_time: current_time,
  }

  # fetching temporary request info
  # return false if temporary request info cannot be found
  redis_req_key_name = redis_record.req_key(get_server_hostname, res_info[:req_object_id])
  req_info = ActiveSupport::HashWithIndifferentAccess.new(redis_record.parsed_get(redis_req_key_name))
  return false if req_info == {}
  redis_record.del redis_req_key_name

  # generating request-response-cycle info
  req_res_info = {
    key_name: nil,
    # server_name: req_info[:server_name],
    server_name: current_hostname,
    api_name: req_info[:req_path],
    api_verb: req_info[:req_http_verb],
    api_controller: req_info[:req_controller],
    api_action: req_info[:req_action],
    request_count: 0,
    min_time: nil,
    max_time: nil,
    avg_time: 0,
    start_time: nil,  # slot starting time
    end_time: nil,  # slot ending time
    error_count: 0,
    min_used_memory_MB: nil,
    max_used_memory_MB: nil,
    avg_used_memory_MB: 0,
    min_swap_memory_MB: nil,
    max_swap_memory_MB: nil,
    avg_swap_memory_MB: 0,
    avg_gc_stat_diff: Hash.new(0),
    min_gc_stat_diff: {},
    max_gc_stat_diff: {},
  }
  redis_req_res_key_name = redis_record.req_res_key(req_res_info[:server_name], req_res_info[:api_name], req_res_info[:api_verb])
  req_res_info[:key_name] = redis_req_res_key_name
  req_res_info[:start_time], req_res_info[:end_time] = redis_record.get_slot_range_for_key(redis_req_res_key_name).map(&:to_s)
  req_res_info_parsed = redis_record.parsed_get(redis_req_res_key_name)
  req_res_info = if req_res_info_parsed.present?
    # making use of existing value from db
    ActiveSupport::HashWithIndifferentAccess.new(req_res_info_parsed)
  else
    # using default value
    ActiveSupport::HashWithIndifferentAccess.new(req_res_info)
  end
  current_cycle_time = (res_info[:res_time] - req_info[:req_time]).round(SECONDS_PRECISION)
  current_gc_stat_diff = get_gc_stat_diff(req_info[:gc_stat], current_gc_stat)
  req_res_info[:min_time] = [req_res_info[:min_time], current_cycle_time].compact.min
  req_res_info[:max_time] = [req_res_info[:max_time], current_cycle_time].compact.max
  req_res_info[:avg_time] = ((req_res_info[:avg_time] * req_res_info[:request_count] + current_cycle_time)/(req_res_info[:request_count] + 1)).round(SECONDS_PRECISION)
  req_res_info[:min_used_memory_MB] = [req_res_info[:min_used_memory_MB], current_used_memory].compact.min
  req_res_info[:max_used_memory_MB] = [req_res_info[:max_used_memory_MB], current_used_memory].compact.max
  req_res_info[:avg_used_memory_MB] = ((req_res_info[:avg_used_memory_MB] * req_res_info[:request_count] + current_used_memory)/(req_res_info[:request_count] + 1)).round(MEMORY_PRECISION)
  req_res_info[:min_swap_memory_MB] = [req_res_info[:min_swap_memory_MB], current_swap_memory].compact.min
  req_res_info[:max_swap_memory_MB] = [req_res_info[:max_swap_memory_MB], current_swap_memory].compact.max
  req_res_info[:avg_swap_memory_MB] = (req_res_info[:avg_swap_memory_MB] * req_res_info[:request_count] + current_swap_memory)/(req_res_info[:request_count] + 1)
  req_res_info[:min_gc_stat_diff] = get_min_max_sum_gc_stat_diff(:min, req_res_info[:min_gc_stat_diff], current_gc_stat_diff)
  req_res_info[:max_gc_stat_diff] = get_min_max_sum_gc_stat_diff(:max, req_res_info[:min_gc_stat_diff], current_gc_stat_diff)
  req_res_info[:avg_gc_stat_diff] = get_avg_gc_stat_diff(req_res_info[:request_count], req_res_info[:min_gc_stat_diff], current_gc_stat_diff)
  req_res_info[:request_count] += 1  # Note: updation of `request_count` should be the last

  # if error is raised
  if capture_error
    req_res_info[:error_count] += 1
  end

  # saving request-respose-cycle info to redis db
  redis_record.jsonified_set(redis_req_res_key_name, req_res_info)

  # return request-response-cycle info key
  redis_req_res_key_name
end

#capture_request_response_cycle_error_infoObject

captures error info



159
160
161
# File 'lib/request_response_stats/request_response.rb', line 159

def capture_request_response_cycle_error_info
  capture_request_response_cycle_end_info(capture_error: true)
end

#capture_request_response_cycle_start_infoObject

captures request info that will be used at the end of request-response cycle



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/request_response_stats/request_response.rb', line 40

def capture_request_response_cycle_start_info
  return gather_stats unless gather_stats

  # get system info
  current_time = get_system_current_time

  # temporarily save request info
  req_info = {
    req_object_id: request.object_id,
    res_object_id: response.object_id,
    server_name: (request.env["SERVER_NAME"] rescue "some_server_name"),
    req_path: (request.path rescue "some_path"),
    req_http_verb: (request.method rescue "some_method"),
    req_time: current_time,  # implicit convertion to integer
    req_url: (request.url rescue "some_url"),
    req_format: (request.parameters["format"] rescue "some_format"),
    req_controller: (request.parameters["controller"] rescue "some_controller"),
    req_action: (request.parameters["action"] rescue "some_action"),
    remote_ip: (request.remote_ip rescue "some_ip"),
    gc_stat: get_gc_stat,
  }
  redis_req_key_name = redis_record.req_key(get_server_hostname, req_info[:req_object_id])
  redis_record.jsonified_set(redis_req_key_name, req_info, {ex: LONGEST_REQ_RES_CYCLE}, {strict_key_check: false})

  # return key_name
  redis_req_key_name
end

#move_data_from_redis_to_mongoObject

moves data from redis to mongo



164
165
166
167
168
169
170
171
172
173
# File 'lib/request_response_stats/request_response.rb', line 164

def move_data_from_redis_to_mongo
  moved_keys = redis_record.freezed_keys.select do |redis_key|
    value = redis_record.formatted_parsed_get_for_mongo(redis_key)
    mongo_doc = mongoid_doc_model.create(value)
    redis_record.del redis_key if mongo_doc
    mongo_doc
  end

  moved_keys.size
end