trailblazer-developer 0.0.27 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|