trailblazer-developer 0.0.27 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -4
- data/CHANGES.md +84 -0
- data/Gemfile +10 -5
- data/lib/trailblazer/developer/debugger/normalizer.rb +60 -0
- data/lib/trailblazer/developer/debugger.rb +97 -0
- data/lib/trailblazer/developer/introspect/graph.rb +83 -0
- data/lib/trailblazer/developer/render/circuit.rb +6 -54
- data/lib/trailblazer/developer/render/linear.rb +1 -1
- data/lib/trailblazer/developer/render/task_wrap.rb +1 -1
- data/lib/trailblazer/developer/trace/node.rb +103 -0
- data/lib/trailblazer/developer/trace/parent_map.rb +32 -0
- data/lib/trailblazer/developer/trace/present.rb +50 -18
- data/lib/trailblazer/developer/trace/snapshot/value.rb +39 -0
- data/lib/trailblazer/developer/trace/snapshot/versions.rb +105 -0
- data/lib/trailblazer/developer/trace/snapshot.rb +71 -0
- data/lib/trailblazer/developer/trace/stack.rb +18 -5
- data/lib/trailblazer/developer/trace.rb +34 -59
- data/lib/trailblazer/developer/version.rb +1 -1
- data/lib/trailblazer/developer/wtf/renderer.rb +8 -8
- data/lib/trailblazer/developer/wtf.rb +47 -47
- data/lib/trailblazer/developer.rb +9 -3
- data/trailblazer-developer.gemspec +2 -3
- metadata +16 -14
- data/.github/workflows/ci_jruby.yml +0 -19
- data/.github/workflows/ci_legacy.yml +0 -19
- data/.github/workflows/ci_truffleruby.yml +0 -19
- data/lib/trailblazer/developer/trace/debugger/normalizer.rb +0 -68
- data/lib/trailblazer/developer/trace/debugger.rb +0 -81
- data/lib/trailblazer/developer/trace/tree.rb +0 -86
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 343ca726bbb8da50f05aca2a552f018ac67683d4c0021226f828a30132ecf85c
|
4
|
+
data.tar.gz: f4ab036e3da016ab3283f258c749ef98cee474a9c69b8303fad36bdd9b6df945
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f66fa880eb4a0673fd881598fe9fa9addebadbd4989fded9366f7960b7ed9ab34794fe8d8a6c4ab3a315b13fe45b048061ac4f4330cb71942a82a3adc1d12a82
|
7
|
+
data.tar.gz: 7ecac119ec1d406442e1ddb5292639bac41e13df24d870f462065d90d5bf690ab4417b0736c9a134b51c2d2929a6952912803cfe2686cfca7d7b16bc14cfaa4a
|
data/.github/workflows/ci.yml
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
## This file is managed by Terraform.
|
2
|
-
## Do not modify this file directly, as it may be overwritten.
|
3
|
-
## Please open an issue instead.
|
4
1
|
name: CI
|
5
2
|
on: [push, pull_request]
|
6
3
|
jobs:
|
@@ -8,7 +5,7 @@ jobs:
|
|
8
5
|
strategy:
|
9
6
|
fail-fast: false
|
10
7
|
matrix:
|
11
|
-
ruby: [2.7, '3.0', '3.1']
|
8
|
+
ruby: [2.6, 2.7, '3.0', '3.1', '3.2', 'jruby']
|
12
9
|
runs-on: ubuntu-latest
|
13
10
|
steps:
|
14
11
|
- uses: actions/checkout@v3
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,87 @@
|
|
1
|
+
# 0.1.0
|
2
|
+
|
3
|
+
* Improvement of tracing performance: factor ~4.
|
4
|
+
* Consistent interface for tracing and presentation.
|
5
|
+
|
6
|
+
## Adding `Debugger` layer
|
7
|
+
|
8
|
+
* Introduce `Debugger::Trace` with `Trace::Node`s and `variable_versions` field
|
9
|
+
to maintain all data produced by tracing in one entity.
|
10
|
+
* In `Trace::Present.call`, you need to pass a block with options for customization instead
|
11
|
+
of passing `:node_options`. Further on, the per-node customization is now keyed by
|
12
|
+
`Trace::Node` instance and not a Stack element anymore.
|
13
|
+
* In `render_method`, remove `:tree` keyword argument, use `:debugger_trace`.
|
14
|
+
* The `render_method` is called with keyword arguments, only. The first positional argument
|
15
|
+
`debugger_nodes` is now `:debugger_trace`.
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
output = Dev::Trace::Present.(
|
19
|
+
stack,
|
20
|
+
node_options: {
|
21
|
+
stack.to_a[0] => {label: "Create"}
|
22
|
+
}
|
23
|
+
)
|
24
|
+
```
|
25
|
+
is now
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
Dev::Trace::Present.(stack) do |trace_nodes:, **|
|
29
|
+
{
|
30
|
+
node_options: {
|
31
|
+
trace_nodes[0] => {label: "Create"}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
## `Trace::Node`
|
38
|
+
|
39
|
+
* `Node.build_for_stack` is now called in `Trace::Present` and produces a list
|
40
|
+
of `Trace::Node` instances that are containers for matching before and after snapshots.
|
41
|
+
* Add `Node::Incomplete` nodes that have no `snapshot_after` as they represent
|
42
|
+
a part of the flow that was canceled.
|
43
|
+
|
44
|
+
## Debugger::Normalizer
|
45
|
+
|
46
|
+
* Deprecate `:captured_node` keyword argument in normalizer steps and
|
47
|
+
rename it to `:trace_node`.
|
48
|
+
* Remove the concept of `:runtime_path` and `:compile_path`. You can always
|
49
|
+
compute paths after or during the debug rendering yourself. However, we simply
|
50
|
+
don't need it for IDE or wtf?.
|
51
|
+
|
52
|
+
## `Trace.wtf?`
|
53
|
+
|
54
|
+
* Rename `:output_data_collector` and `:input_data_collector` to `:after_snapshooter` and `:before_snapshooter`
|
55
|
+
as they both produce instances of `Snapshot`. The return signature has changed, the snapshooters
|
56
|
+
return two values now: the data object and an object representing the variable versioning.
|
57
|
+
|
58
|
+
## Snapshot
|
59
|
+
|
60
|
+
* Introduce the concept of `Snapshot` which can be a moment in time before or after a step.
|
61
|
+
The `Snapshot::Before` and `Snapshot::After` (renamed from `Captured::Input` and `Captured::Output`)
|
62
|
+
are created during the tracing and after tracing associated to a specific `Trace::Node`.
|
63
|
+
* Add `Snapshot::Ctx` and `Snapshot::Versions` which is a new, faster way of capturing variables.
|
64
|
+
Instead of always calling `ctx.inspect` for every trace step, only variables that have changed
|
65
|
+
are snapshooted using the (configurable) `:value_snapshooter`. This improves `#wtf?` performance
|
66
|
+
up to factor 10.
|
67
|
+
|
68
|
+
# 0.0.29
|
69
|
+
|
70
|
+
* The `:render_method` callable can now return output along with additional returned values.
|
71
|
+
* Pass the top-level `:activity` into the `:render_method`.
|
72
|
+
* In `#wtf?`, only complete the stack when an exception occurred.
|
73
|
+
* `wtf?` now returns all original return values plus the computed output from `:render_method`,
|
74
|
+
followed by an arbitrary object from the rendering code.
|
75
|
+
|
76
|
+
# 0.0.28
|
77
|
+
|
78
|
+
* Move `Introspect::Graph` over from `trailblazer-activity`. It's a data structure very specific
|
79
|
+
to rendering, which is not a part of pure runtime behavior.
|
80
|
+
* Use `Introspect.Nodes` API instead of `TaskMap`.
|
81
|
+
* Remove `:task_maps_per_activity` in `Debugger` as we don't need to cache anymore.
|
82
|
+
* Require `trailblazer-activity-dsl-linear-1.2.0`.
|
83
|
+
* Remove `Render::Circuit` as this is part of `trailblazer-activity` now.
|
84
|
+
|
1
85
|
# 0.0.27
|
2
86
|
|
3
87
|
## Trace
|
data/Gemfile
CHANGED
@@ -3,8 +3,13 @@ source "https://rubygems.org"
|
|
3
3
|
# Specify your gem's dependencies in trailblazer-developer.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
# gem "trailblazer-activity", "
|
7
|
-
# gem "trailblazer-activity",
|
8
|
-
# gem "trailblazer-
|
9
|
-
gem "trailblazer-
|
10
|
-
|
6
|
+
# gem "trailblazer-activity", path: "../trailblazer-activity"
|
7
|
+
# gem "trailblazer-activity-dsl-linear", path: "../trailblazer-activity-dsl-linear"
|
8
|
+
# gem "trailblazer-operation", path: "../trailblazer-operation"
|
9
|
+
# gem "trailblazer-pro", path: "../trailblazer-pro"
|
10
|
+
|
11
|
+
# gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
|
12
|
+
# gem "trailblazer-activity-dsl-linear", github: "trailblazer/trailblazer-activity-dsl-linear"
|
13
|
+
|
14
|
+
|
15
|
+
gem "benchmark-ips"
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
module Developer
|
3
|
+
module Debugger
|
4
|
+
# @private
|
5
|
+
# Public entry point to add Debugger::Node normalizer steps.
|
6
|
+
def self.add_normalizer_step!(step, id:, normalizer: Normalizer::PIPELINES.last, **options)
|
7
|
+
task = Normalizer.Task(step)
|
8
|
+
|
9
|
+
# We have a TaskWrap::Pipeline (a very simple style of "activity" used for normalizers) and
|
10
|
+
# add another step using the "friendly interface" from {Activity::Adds}.
|
11
|
+
options = {append: nil} unless options.any?
|
12
|
+
|
13
|
+
pipeline_extension = Activity::TaskWrap::Extension.build([task, id: id, **options])
|
14
|
+
|
15
|
+
Normalizer::PIPELINES << pipeline_extension.(normalizer)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Run at runtime when preparing a Trace::Nodes for presentation.
|
19
|
+
module Normalizer
|
20
|
+
def self.Task(user_step) # TODO: we could keep this in the {activity} gem.
|
21
|
+
Activity::TaskWrap::Pipeline::TaskAdapter.for_step(user_step, option: false) # we don't need Option as we don't have ciruit_options here, and no {:exec_context}
|
22
|
+
end
|
23
|
+
|
24
|
+
# Default steps for the Debugger::Node options pipeline, following the step-interface.
|
25
|
+
module Default
|
26
|
+
def self.compile_id(ctx, activity:, task:, **)
|
27
|
+
ctx[:compile_id] = Activity::Introspect.Nodes(activity, task: task)[:id]
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.runtime_id(ctx, compile_id:, **)
|
31
|
+
ctx[:runtime_id] = compile_id
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.label(ctx, label: nil, runtime_id:, **)
|
35
|
+
ctx[:label] = label || runtime_id
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.data(ctx, data: {}, **)
|
39
|
+
ctx[:data] = data
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.incomplete?(ctx, trace_node:, **)
|
43
|
+
ctx[:incomplete?] = trace_node.is_a?(Developer::Trace::Node::Incomplete)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
default_steps = {
|
48
|
+
compile_id: Normalizer.Task(Default.method(:compile_id)),
|
49
|
+
runtime_id: Normalizer.Task(Default.method(:runtime_id)),
|
50
|
+
label: Normalizer.Task(Default.method(:label)),
|
51
|
+
data: Normalizer.Task(Default.method(:data)),
|
52
|
+
incomplete?: Normalizer.Task(Default.method(:incomplete?)),
|
53
|
+
}.
|
54
|
+
collect { |id, task| Activity::TaskWrap::Pipeline.Row(id, task) }
|
55
|
+
|
56
|
+
PIPELINES = [Activity::TaskWrap::Pipeline.new(default_steps)] # we do mutate this constant at compile-time. Maybe # DISCUSS and find a better way.
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
module Developer
|
3
|
+
# Code in Debugger is only executed if the user wants to render the stack.
|
4
|
+
module Debugger
|
5
|
+
ATTRS = [
|
6
|
+
:id,
|
7
|
+
:trace_node,
|
8
|
+
:task,
|
9
|
+
:activity,
|
10
|
+
:compile_id,
|
11
|
+
:runtime_id,
|
12
|
+
:label,
|
13
|
+
:data,
|
14
|
+
:snapshot_before,
|
15
|
+
:snapshot_after,
|
16
|
+
:level,
|
17
|
+
:incomplete?,
|
18
|
+
:captured_node, # TODO: remove once macro is 2.2
|
19
|
+
]
|
20
|
+
|
21
|
+
# The {Debugger::Node} is an abstraction between Trace::Node and the actual rendering layer (why?)
|
22
|
+
#
|
23
|
+
# TODO: class, "type",
|
24
|
+
# which track, return signal, etc
|
25
|
+
class Node < Struct.new(*ATTRS, keyword_init: true)
|
26
|
+
# we always key options for specific nodes by Stack::Captured::Input, so we don't confuse activities if they were called multiple times.
|
27
|
+
#
|
28
|
+
# @return [Debugger::Node] array of Debugger::Node
|
29
|
+
def self.build(trace_nodes, node_options: {}, normalizer: Debugger::Normalizer::PIPELINES.last, **options_for_nodes)
|
30
|
+
# DISCUSS: this might change if we introduce a new Node type for Trace.
|
31
|
+
_debugger_nodes = trace_nodes.collect do |trace_node|
|
32
|
+
# it's possible to pass per-node options, like {label: "Yo!"} via {:node_options[<snapshot_before>]}
|
33
|
+
options_from_user = node_options[trace_node] || {}
|
34
|
+
|
35
|
+
options_from_trace_node = trace_node
|
36
|
+
.to_h # :level, :snapshot_before, :snapshot_after
|
37
|
+
.merge(
|
38
|
+
id: trace_node.object_id,
|
39
|
+
trace_node: trace_node,
|
40
|
+
activity: trace_node.snapshot_before.activity,
|
41
|
+
task: trace_node.task,
|
42
|
+
captured_node: DeprecatedCapturedNode, # TODO: remove once macro is 2.2
|
43
|
+
)
|
44
|
+
|
45
|
+
options_for_debugger_node, _ = normalizer.(
|
46
|
+
{
|
47
|
+
**options_from_trace_node,
|
48
|
+
**options_from_user
|
49
|
+
},
|
50
|
+
[]
|
51
|
+
)
|
52
|
+
|
53
|
+
# these attributes are not changing with the presentation
|
54
|
+
Debugger::Node.new(**options_for_debugger_node).freeze
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# TODO: remove once macro is 2.2
|
59
|
+
class DeprecatedCapturedNode
|
60
|
+
def self.method_missing(*)
|
61
|
+
raise "[Trailblazer] The `:captured_node` argument is deprecated, please upgrade to `trailblazer-developer-0.1.0` and use `:trace_node` if the upgrade doesn't fix it."
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end # Node
|
65
|
+
|
66
|
+
# Interface for data (nodes, versions, etc) between tracing code and presentation layer.
|
67
|
+
# We have no concept of {Stack} here anymore. Nodes and arbitrary objects such as "versions".
|
68
|
+
# Debugger::Trace interface abstracts away the fact we have two snapshots. Here,
|
69
|
+
# we only have a node per task.
|
70
|
+
#
|
71
|
+
class Trace
|
72
|
+
# Called in {Trace::Present}.
|
73
|
+
# Design note: goal here is to have as little computation as possible.
|
74
|
+
def self.build(stack, trace_nodes, **options_for_debugger_nodes)
|
75
|
+
nodes = Debugger::Node.build(
|
76
|
+
trace_nodes,
|
77
|
+
**options_for_debugger_nodes,
|
78
|
+
)
|
79
|
+
|
80
|
+
new(nodes: nodes, variable_versions: stack.variable_versions) # after this, the concept of "Stack" doesn't exist anymore.
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize(nodes:, variable_versions:)
|
84
|
+
@options = {nodes: nodes, variable_versions: variable_versions}
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_h
|
88
|
+
@options
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_a
|
92
|
+
to_h[:nodes].to_a
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end # Debugger
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# NOTE: The Graph API might get deprecated and replaced.
|
2
|
+
module Trailblazer
|
3
|
+
module Developer
|
4
|
+
module Introspect
|
5
|
+
# TODO: order of step/fail/pass in Node would be cool to have
|
6
|
+
|
7
|
+
# TODO: Remove Graph. This is only useful to render the full circuit
|
8
|
+
# Some thoughts here:
|
9
|
+
# * where do we need Schema.outputs? and where task.outputs?
|
10
|
+
#
|
11
|
+
#
|
12
|
+
# @private This API is still under construction.
|
13
|
+
class Graph
|
14
|
+
def initialize(activity)
|
15
|
+
@schema = activity.to_h or raise
|
16
|
+
@circuit = @schema[:circuit]
|
17
|
+
@map = @circuit.to_h[:map]
|
18
|
+
@configs = @schema[:nodes]
|
19
|
+
end
|
20
|
+
|
21
|
+
def find(id = nil, &block)
|
22
|
+
return find_by_id(id) unless block_given?
|
23
|
+
|
24
|
+
find_with_block(&block)
|
25
|
+
end
|
26
|
+
|
27
|
+
# TODO: convert to {#to_a}.
|
28
|
+
def collect(strategy: :circuit)
|
29
|
+
@map.keys.each_with_index.collect { |task, i| yield find_with_block { |node| node.task == task }, i }
|
30
|
+
end
|
31
|
+
|
32
|
+
def stop_events
|
33
|
+
@circuit.to_h[:end_events]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def find_by_id(id)
|
39
|
+
node = @configs.find { |_, _node| _node.id == id } or return
|
40
|
+
node_for(node[1])
|
41
|
+
end
|
42
|
+
|
43
|
+
def find_with_block
|
44
|
+
existing = @configs.find { |_, node| yield Node(node.task, node.id, node.outputs, node.data) } or return
|
45
|
+
|
46
|
+
node_for(existing[1])
|
47
|
+
end
|
48
|
+
|
49
|
+
# Build a {Graph::Node} with outputs etc.
|
50
|
+
def node_for(node_attributes)
|
51
|
+
Node(
|
52
|
+
node_attributes.task,
|
53
|
+
node_attributes.id,
|
54
|
+
node_attributes.outputs, # [#<struct Trailblazer::Activity::Output signal=Trailblazer::Activity::Right, semantic=:success>]
|
55
|
+
outgoings_for(node_attributes),
|
56
|
+
node_attributes.data,
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def Node(*args)
|
61
|
+
Node.new(*args).freeze
|
62
|
+
end
|
63
|
+
|
64
|
+
Node = Struct.new(:task, :id, :outputs, :outgoings, :data)
|
65
|
+
Outgoing = Struct.new(:output, :task)
|
66
|
+
|
67
|
+
def outgoings_for(node)
|
68
|
+
outputs = node.outputs
|
69
|
+
connections = @map[node.task]
|
70
|
+
|
71
|
+
connections.collect do |signal, target|
|
72
|
+
output = outputs.find { |out| out.signal == signal }
|
73
|
+
Outgoing.new(output, target)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.Graph(*args)
|
79
|
+
Graph.new(*args)
|
80
|
+
end
|
81
|
+
end # Graph
|
82
|
+
end
|
83
|
+
end
|
@@ -2,61 +2,13 @@ module Trailblazer
|
|
2
2
|
module Developer
|
3
3
|
module_function
|
4
4
|
|
5
|
-
def render(activity, **options)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
module Render
|
10
|
-
module Circuit
|
11
|
-
module_function
|
12
|
-
|
13
|
-
# Render an {Activity}'s circuit as a simple hash.
|
14
|
-
def call(activity, path: nil, **options)
|
15
|
-
if path # TODO: move to place where all renderers can use this logic!
|
16
|
-
node, _, graph = Developer::Introspect.find_path(activity, path)
|
17
|
-
activity = node.task
|
18
|
-
end
|
19
|
-
|
20
|
-
graph = Activity::Introspect::Graph(activity)
|
21
|
-
|
22
|
-
circuit_hash(graph, **options)
|
23
|
-
end
|
24
|
-
|
25
|
-
def circuit_hash(graph, **options)
|
26
|
-
content = graph.collect do |node|
|
27
|
-
conns = node.outgoings.collect do |outgoing|
|
28
|
-
" {#{outgoing.output.signal}} => #{inspect_with_matcher(outgoing.task, **options)}"
|
29
|
-
end
|
30
|
-
|
31
|
-
[ inspect_with_matcher(node.task, **options), conns.join("\n") ]
|
32
|
-
end
|
33
|
-
|
34
|
-
content = content.join("\n")
|
35
|
-
|
36
|
-
"\n#{content}".gsub(/0x\w+/, "0x")
|
37
|
-
end
|
38
|
-
|
39
|
-
# If Ruby had pattern matching, this function wasn't necessary.
|
40
|
-
def inspect_with_matcher(task, inspect_task: method(:inspect_task), inspect_end: method(:inspect_end))
|
41
|
-
return inspect_task.(task) unless task.kind_of?(Trailblazer::Activity::End)
|
42
|
-
inspect_end.(task)
|
43
|
-
end
|
44
|
-
|
45
|
-
def inspect_task(task)
|
46
|
-
task.inspect
|
47
|
-
end
|
48
|
-
|
49
|
-
def inspect_end(task)
|
50
|
-
class_name = Render::Circuit.strip(task.class)
|
51
|
-
options = task.to_h
|
52
|
-
|
53
|
-
"#<#{class_name}/#{options[:semantic].inspect}>"
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.strip(string)
|
57
|
-
string.to_s.sub("Trailblazer::Activity::", "")
|
58
|
-
end
|
5
|
+
def render(activity, path: nil, **options)
|
6
|
+
if path # TODO: move to place where all renderers can use this logic!
|
7
|
+
node, _, graph = Developer::Introspect.find_path(activity, path)
|
8
|
+
activity = node.task
|
59
9
|
end
|
10
|
+
|
11
|
+
Activity::Introspect::Render.(activity, **options)
|
60
12
|
end
|
61
13
|
end
|
62
14
|
end
|
@@ -15,7 +15,7 @@ module Trailblazer
|
|
15
15
|
module_function
|
16
16
|
|
17
17
|
def call(operation, style: :line)
|
18
|
-
graph =
|
18
|
+
graph = Introspect::Graph(operation)
|
19
19
|
|
20
20
|
rows = graph.collect do |node, i|
|
21
21
|
next if node[:data][:stop_event] # DISCUSS: show this?
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
module Developer
|
3
|
+
module Trace
|
4
|
+
# Build array of {Trace::Node} from a snapshots stack.
|
5
|
+
# @private
|
6
|
+
def self.build_nodes(snapshots)
|
7
|
+
instructions = [
|
8
|
+
[0, snapshots]
|
9
|
+
]
|
10
|
+
|
11
|
+
_nodes = Node.process_instructions(instructions)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Datastructure representing a trace.
|
15
|
+
class Node < Struct.new(:level, :task, :snapshot_before, :snapshot_after)
|
16
|
+
class Incomplete < Node
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.pop_from_instructions!(instructions)
|
20
|
+
while (level, remaining_snapshots = instructions.pop)
|
21
|
+
next if level.nil?
|
22
|
+
next if remaining_snapshots.empty?
|
23
|
+
|
24
|
+
return level, remaining_snapshots
|
25
|
+
end
|
26
|
+
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
# def self.BLA(instructions)
|
31
|
+
# instructions.collect do |(level, remaining_snapshots)|
|
32
|
+
# [
|
33
|
+
# level,
|
34
|
+
# remaining_snapshots.collect { |snap| [snap.class, snap.task] }
|
35
|
+
# ]
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
|
39
|
+
def self.process_instructions(instructions) # FIXME: mutating argument
|
40
|
+
nodes = []
|
41
|
+
|
42
|
+
while (level, remaining_snapshots = pop_from_instructions!(instructions))
|
43
|
+
raise unless remaining_snapshots[0].is_a?(Snapshot::Before) # DISCUSS: remove assertion?
|
44
|
+
|
45
|
+
node, new_instructions = node_and_instructions_for(remaining_snapshots[0], remaining_snapshots[1..-1], level: level)
|
46
|
+
# pp BLA(new_instructions)
|
47
|
+
|
48
|
+
nodes << node
|
49
|
+
|
50
|
+
instructions += new_instructions
|
51
|
+
end
|
52
|
+
|
53
|
+
return nodes
|
54
|
+
end
|
55
|
+
|
56
|
+
# Called per snapshot_before "process_branch"
|
57
|
+
# 1. Find, for snapshot_before, the matching snapshot_after in the stack
|
58
|
+
# 2. Extract snapshots inbetween those two. These are min. 1 level deeper in!
|
59
|
+
# 3. Run process_siblings for 2.
|
60
|
+
def self.node_and_instructions_for(snapshot_before, descendants, level:)
|
61
|
+
# Find closing snapshot for this branch.
|
62
|
+
snapshot_after = descendants.find do |snapshot|
|
63
|
+
snapshot.is_a?(Snapshot::After) && snapshot.data[:snapshot_before] == snapshot_before
|
64
|
+
end
|
65
|
+
|
66
|
+
if snapshot_after
|
67
|
+
snapshot_after_index = descendants.index(snapshot_after)
|
68
|
+
|
69
|
+
instructions =
|
70
|
+
if snapshot_after_index == 0 # E.g. before/Start, after/Start
|
71
|
+
[
|
72
|
+
[level, descendants[1..-1]]
|
73
|
+
]
|
74
|
+
else
|
75
|
+
nested_instructions = [
|
76
|
+
# instruction to go through the remaining, behind this tuple.
|
77
|
+
[
|
78
|
+
level,
|
79
|
+
descendants[(snapshot_after_index + 1)..-1]
|
80
|
+
],
|
81
|
+
# instruction to go through all snapshots between this current tuple.
|
82
|
+
[
|
83
|
+
level + 1,
|
84
|
+
descendants[0..snapshot_after_index - 1], # "new descendants"
|
85
|
+
],
|
86
|
+
]
|
87
|
+
end
|
88
|
+
|
89
|
+
node = new(level, snapshot_before.task, snapshot_before, snapshot_after)
|
90
|
+
else # incomplete
|
91
|
+
instructions = [
|
92
|
+
[level + 1, descendants]
|
93
|
+
]
|
94
|
+
|
95
|
+
node = Incomplete.new(level, snapshot_before.task, snapshot_before, nil)
|
96
|
+
end
|
97
|
+
|
98
|
+
return node, instructions
|
99
|
+
end
|
100
|
+
end # Node
|
101
|
+
end
|
102
|
+
end # Developer
|
103
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
module Developer
|
3
|
+
module Trace
|
4
|
+
# Map each {Node} instance to its parent {Node}.
|
5
|
+
module ParentMap # DISCUSS: where does this belong?
|
6
|
+
def self.build(trace_nodes)
|
7
|
+
levels = {}
|
8
|
+
trace_nodes.collect do |node|
|
9
|
+
level = node.level
|
10
|
+
levels[level] = node
|
11
|
+
|
12
|
+
[node, levels[level - 1]]
|
13
|
+
end.to_h
|
14
|
+
end
|
15
|
+
|
16
|
+
# @public
|
17
|
+
def self.path_for(parent_map, node)
|
18
|
+
path = []
|
19
|
+
|
20
|
+
while parent = parent_map[node] # DISCUSS: what if the graphs are cached and present, already?
|
21
|
+
node_id = Activity::Introspect.Nodes(node.snapshot_before.activity, task: node.snapshot_before.task).id
|
22
|
+
path << node_id
|
23
|
+
|
24
|
+
node = parent
|
25
|
+
end
|
26
|
+
|
27
|
+
path.reverse
|
28
|
+
end
|
29
|
+
end # ParentMap
|
30
|
+
end
|
31
|
+
end # Developer
|
32
|
+
end
|