Class: Upkeep::Runtime::Recorder

Inherits:
Object
  • Object
show all
Defined in:
lib/upkeep/runtime.rb

Defined Under Namespace

Classes: RefusedBoundary

Constant Summary collapse

REQUEST_NODE_ID =
:request

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(graph: nil, profile: false) ⇒ Recorder

Returns a new instance of Recorder.



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/upkeep/runtime.rb', line 82

def initialize(graph: nil, profile: false)
  @frame_stack = []
  @graph = graph || DAG::Graph.new
  @profile = profile
  @profile_timings = Hash.new(0.0)
  @profile_counts = Hash.new(0)
  @refused_boundaries = []
  @ambient_replay_inputs_by_owner = Hash.new do |owners, owner_id|
    owners[owner_id] = Hash.new { |sources, source| sources[source] = {} }
  end
  @pending_dependencies_by_owner = Hash.new { |owners, owner_id| owners[owner_id] = {} }
  @relation_provenance_by_collection_id = {}
  @graph.add_node(REQUEST_NODE_ID, kind: :request, payload: {}) unless @graph.node?(REQUEST_NODE_ID)
  @subscription_shape_trace = DAG::SubscriptionShape::Trace.new(graph_version: @graph.version)
end

Instance Attribute Details

#graphObject (readonly)

Returns the value of attribute graph.



80
81
82
# File 'lib/upkeep/runtime.rb', line 80

def graph
  @graph
end

#refused_boundariesObject (readonly)

Returns the value of attribute refused_boundaries.



80
81
82
# File 'lib/upkeep/runtime.rb', line 80

def refused_boundaries
  @refused_boundaries
end

Class Method Details

.from_h(snapshot) ⇒ Object



98
99
100
101
# File 'lib/upkeep/runtime.rb', line 98

def self.from_h(snapshot)
  snapshot = Dependencies.symbolize_keys(snapshot)
  new(graph: DAG::Graph.from_h(snapshot.fetch(:graph)))
end

Instance Method Details

#ambient_replay_inputs_for(owner_id) ⇒ Object



165
166
167
168
169
# File 'lib/upkeep/runtime.rb', line 165

def ambient_replay_inputs_for(owner_id)
  @ambient_replay_inputs_by_owner.fetch(owner_id, {}).each_with_object({}) do |(source, values), inputs|
    inputs[source] = values.dup
  end
end

#current_frameObject



213
214
215
# File 'lib/upkeep/runtime.rb', line 213

def current_frame
  @frame_stack.last
end

#current_ownerObject



217
218
219
# File 'lib/upkeep/runtime.rb', line 217

def current_owner
  current_frame || REQUEST_NODE_ID
end

#flush_pending_dependencies(owner_id = nil) ⇒ Object



133
134
135
136
137
138
139
140
141
# File 'lib/upkeep/runtime.rb', line 133

def flush_pending_dependencies(owner_id = nil)
  if owner_id
    flush_pending_dependencies_for(owner_id)
  else
    @pending_dependencies_by_owner.keys.each { |pending_owner_id| flush_pending_dependencies_for(pending_owner_id) }
  end
ensure
  @pending_dependencies_by_owner.delete(owner_id) if owner_id
end

#identity_profile(frame_id) ⇒ Object



221
222
223
224
# File 'lib/upkeep/runtime.rb', line 221

def identity_profile(frame_id)
  flush_pending_dependencies
  identity_dependencies_for(frame_id).map(&:to_h)
end

#identity_signature(frame_id) ⇒ Object



226
227
228
229
230
231
232
# File 'lib/upkeep/runtime.rb', line 226

def identity_signature(frame_id)
  flush_pending_dependencies
  identity_dependencies = identity_dependencies_for(frame_id)
  return "public" if identity_dependencies.empty?

  Digest::SHA256.hexdigest(identity_dependencies.map(&:identity_key).sort_by(&:inspect).inspect)[0, 16]
end

#profile_countsObject



205
206
207
# File 'lib/upkeep/runtime.rb', line 205

def profile_counts
  @profile_counts.dup
end

#profile_timingsObject



201
202
203
# File 'lib/upkeep/runtime.rb', line 201

def profile_timings
  @profile_timings.transform_values { |value| value.round(3) }
end

#reactive?Boolean

Returns:

  • (Boolean)


209
210
211
# File 'lib/upkeep/runtime.rb', line 209

def reactive?
  @refused_boundaries.empty?
end

#record_ambient_replay_input(source, key, value) ⇒ Object



158
159
160
161
162
163
# File 'lib/upkeep/runtime.rb', line 158

def record_ambient_replay_input(source, key, value)
  profile_count(:recorder_ambient_replay_input_count)
  profile_timing(:recorder_ambient_replay_input_ms) do
    @ambient_replay_inputs_by_owner[current_owner][source.to_sym][key.to_s] = value
  end
end

#record_dependency(dependency) ⇒ Object



143
144
145
146
147
148
149
# File 'lib/upkeep/runtime.rb', line 143

def record_dependency(dependency)
  profile_count(:recorder_dependency_count)
  profile_timing(:recorder_dependency_ms) do
    owner_id = current_owner
    @pending_dependencies_by_owner[owner_id][dependency.cache_key] ||= dependency
  end
end

#record_relation_provenance(collection, model_name:, analysis:) ⇒ Object



171
172
173
174
175
176
177
178
179
# File 'lib/upkeep/runtime.rb', line 171

def record_relation_provenance(collection, model_name:, analysis:)
  return unless collection && analysis

  profile_count(:recorder_relation_provenance_count)
  profile_timing(:recorder_relation_provenance_ms) do
    @relation_provenance_by_collection_id[collection.object_id] =
      RelationProvenance.new(model_name.to_s, analysis)
  end
end

#refuse_boundary(reason:, message:, suggestions:, source:) ⇒ Object



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/upkeep/runtime.rb', line 185

def refuse_boundary(reason:, message:, suggestions:, source:)
  profile_count(:recorder_refused_boundary_count)
  profile_timing(:recorder_refused_boundary_ms) do
    boundary = RefusedBoundary.new(
      reason.to_s,
      message.to_s,
      Array(suggestions).map(&:to_s),
      source.to_s
    )
    return false if @refused_boundaries.include?(boundary)

    @refused_boundaries << boundary
    true
  end
end

#relation_provenance_for(collection) ⇒ Object



181
182
183
# File 'lib/upkeep/runtime.rb', line 181

def relation_provenance_for(collection)
  @relation_provenance_by_collection_id[collection.object_id] if collection
end

#subscription_shape(request_signature: nil) ⇒ Object



151
152
153
154
155
156
# File 'lib/upkeep/runtime.rb', line 151

def subscription_shape(request_signature: nil)
  flush_pending_dependencies
  return @subscription_shape_trace.subscription_shape(request_signature: request_signature) if @subscription_shape_trace.covers?(@graph)

  DAG::SubscriptionShape.from_graph(@graph, request_signature: request_signature)
end

#to_h(dependencies: :all) ⇒ Object



103
104
105
106
# File 'lib/upkeep/runtime.rb', line 103

def to_h(dependencies: :all)
  flush_pending_dependencies
  { graph: graph.to_h(dependencies: dependencies) }
end

#to_persistent_hObject



108
109
110
# File 'lib/upkeep/runtime.rb', line 108

def to_persistent_h
  to_h(dependencies: :identity)
end

#with_frame(frame_id, metadata) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/upkeep/runtime.rb', line 112

def with_frame(frame_id, )
  profile_count(:recorder_frame_count)
  parent_id = nil
  profile_timing(:recorder_frame_ms) do
    invalidate_subscription_shape_trace_if_needed
    parent_id = current_owner
    @graph.add_node(frame_id, kind: :frame, payload: )
    @graph.add_edge(parent_id, frame_id, reason: :contains)
    profile_timing(:recorder_shape_trace_ms) do
      @subscription_shape_trace.record_frame(frame_id, , parent_id: parent_id, graph_version: @graph.version)
    end
  end
  @frame_stack.push(frame_id)
  begin
    yield
  ensure
    flush_pending_dependencies(frame_id)
    @frame_stack.pop
  end
end