From 57251b53b2ed4965a075042b07f9589699855daf Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Sat, 11 Jan 2025 13:52:02 +0700 Subject: [PATCH 01/11] Add base builder and extractor classes for diagram builders; implement message and participant extractors --- .../diagram_builders/base_builder.rb | 17 ++++++ .../extractors/base_extractor.rb | 19 ++++++ .../extractors/message_extractor.rb | 45 ++++++++++++++ .../extractors/participant_extractor.rb | 58 +++++++++++++++++++ .../diagram_builders/mermaid/base_builder.rb | 12 ++++ .../mermaid/sequence_builder.rb | 26 +++++++++ lib/trace_viz/loggers/trace_logger.rb | 2 +- lib/trace_viz/renderers/summary_renderer.rb | 17 ++---- lib/trace_viz/renderers/verbose_renderer.rb | 2 +- lib/trace_viz/trace_data/base.rb | 7 +++ lib/trace_viz/trace_data/summary_node.rb | 4 ++ lib/trace_viz/trace_data/trace_point/base.rb | 4 ++ .../transformers/base_transformer.rb | 2 + .../transformers/summary_transformer.rb | 23 ++++---- lib/trace_viz/utils/hierarchy_flattener.rb | 27 +++++++++ 15 files changed, 240 insertions(+), 25 deletions(-) create mode 100644 lib/trace_viz/diagram_builders/base_builder.rb create mode 100644 lib/trace_viz/diagram_builders/extractors/base_extractor.rb create mode 100644 lib/trace_viz/diagram_builders/extractors/message_extractor.rb create mode 100644 lib/trace_viz/diagram_builders/extractors/participant_extractor.rb create mode 100644 lib/trace_viz/diagram_builders/mermaid/base_builder.rb create mode 100644 lib/trace_viz/diagram_builders/mermaid/sequence_builder.rb create mode 100644 lib/trace_viz/utils/hierarchy_flattener.rb diff --git a/lib/trace_viz/diagram_builders/base_builder.rb b/lib/trace_viz/diagram_builders/base_builder.rb new file mode 100644 index 0000000..62e53ef --- /dev/null +++ b/lib/trace_viz/diagram_builders/base_builder.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module TraceViz + module DiagramBuilders + class BaseBuilder + attr_reader :collector + + def initialize(collector) + @collector = collector + end + + def build + raise NotImplementedError + end + end + end +end diff --git a/lib/trace_viz/diagram_builders/extractors/base_extractor.rb b/lib/trace_viz/diagram_builders/extractors/base_extractor.rb new file mode 100644 index 0000000..64dd4d3 --- /dev/null +++ b/lib/trace_viz/diagram_builders/extractors/base_extractor.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module TraceViz + module DiagramBuilders + module Extractors + class BaseExtractor + attr_reader :collector + + def initialize(collector) + @collector = collector + end + + def extract + raise NotImplementedError + end + end + end + end +end diff --git a/lib/trace_viz/diagram_builders/extractors/message_extractor.rb b/lib/trace_viz/diagram_builders/extractors/message_extractor.rb new file mode 100644 index 0000000..be99ba8 --- /dev/null +++ b/lib/trace_viz/diagram_builders/extractors/message_extractor.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "trace_viz/utils/hierarchy_flattener" +require_relative "base_extractor" + +module TraceViz + module DiagramBuilders + module Extractors + class MessageExtractor < BaseExtractor + def initialize(collector, participants) + super(collector) + @participants = participants + end + + def extract + nodes_without_root.map { |node| build_message(node.data) } + end + + private + + attr_reader :participants + + def data + @data ||= Transformers::SummaryTransformer.new(collector).transform + end + + def nodes + @nodes ||= Utils::HierarchyFlattener.flatten(data) + end + + def nodes_without_root + nodes.drop(1) + end + + def build_message(data) + { + from: participants[data.klass], + to: participants[data.klass], + message: data.action, + } + end + end + end + end +end diff --git a/lib/trace_viz/diagram_builders/extractors/participant_extractor.rb b/lib/trace_viz/diagram_builders/extractors/participant_extractor.rb new file mode 100644 index 0000000..8e85ffa --- /dev/null +++ b/lib/trace_viz/diagram_builders/extractors/participant_extractor.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require_relative "base_extractor" + +module TraceViz + module DiagramBuilders + module Extractors + class ParticipantExtractor < BaseExtractor + def extract + participants = [] + data.map do |node| + participants << node.klass + end + + generate_aliases(participants.uniq) + end + + private + + def data + collector.collection + end + + def generate_aliases(participants) + aliases = {} + participants.each do |participant| + aliases[participant] = generate_alias(participant, aliases) + end + aliases + end + + def generate_alias(klass, existing_aliases = {}) + # Convert class to string + klass_name = klass.to_s + + # Split into namespace components + parts = klass_name.split("::") + + # Extract uppercase letters from each part + initials = parts.map { |part| part.scan(/[A-Z]/).join } + + # Join initials with `_` to preserve hierarchy + alias_candidate = initials.join("_") + + # Ensure uniqueness + unique_alias = alias_candidate + counter = 1 + while existing_aliases.value?(unique_alias) + unique_alias = "#{alias_candidate}_#{counter}" + counter += 1 + end + + unique_alias + end + end + end + end +end diff --git a/lib/trace_viz/diagram_builders/mermaid/base_builder.rb b/lib/trace_viz/diagram_builders/mermaid/base_builder.rb new file mode 100644 index 0000000..c35c1d0 --- /dev/null +++ b/lib/trace_viz/diagram_builders/mermaid/base_builder.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require_relative "../base_builder" + +module TraceViz + module DiagramBuilders + module Mermaid + class BaseBuilder < DiagramBuilders::BaseBuilder + end + end + end +end diff --git a/lib/trace_viz/diagram_builders/mermaid/sequence_builder.rb b/lib/trace_viz/diagram_builders/mermaid/sequence_builder.rb new file mode 100644 index 0000000..e18e13f --- /dev/null +++ b/lib/trace_viz/diagram_builders/mermaid/sequence_builder.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require_relative "base_builder" +require_relative "../extractors/participant_extractor" +require_relative "../extractors/message_extractor" + +module TraceViz + module DiagramBuilders + module Mermaid + class SequenceBuilder < BaseBuilder + def build + participant_extractor = Extractors::ParticipantExtractor.new(collector) + participants = participant_extractor.extract + + message_extractor = Extractors::MessageExtractor.new(collector, participants) + messages = message_extractor.extract + + { + participants: participants, + messages: messages, + } + end + end + end + end +end diff --git a/lib/trace_viz/loggers/trace_logger.rb b/lib/trace_viz/loggers/trace_logger.rb index 2856208..cee4ffc 100644 --- a/lib/trace_viz/loggers/trace_logger.rb +++ b/lib/trace_viz/loggers/trace_logger.rb @@ -31,7 +31,7 @@ def formatted_message end def fetch_formatter(trace_data) - formatter_factory.fetch_formatter(trace_data.event) + formatter_factory.fetch_formatter(trace_data.key) end end end diff --git a/lib/trace_viz/renderers/summary_renderer.rb b/lib/trace_viz/renderers/summary_renderer.rb index c0d7e2e..586757e 100644 --- a/lib/trace_viz/renderers/summary_renderer.rb +++ b/lib/trace_viz/renderers/summary_renderer.rb @@ -2,13 +2,12 @@ require_relative "base_renderer" require "trace_viz/transformers/summary_transformer" -require "trace_viz/trace_data/summary_node" module TraceViz module Renderers class SummaryRenderer < BaseRenderer def to_lines - render_nodes(data) + render_nodes(data.children) end private @@ -22,21 +21,13 @@ def render_nodes(nodes) end def render_node(node) - node_line = NodeLine.new(node[:data], format_node(node[:data])) + node_line = NodeLine.new(node.data, format_node(node.data)) - [node_line] + render_nodes(node[:children]) + [node_line] + render_nodes(node.children) end def format_node(data) - if data.is_a?(TraceData::SummaryNode) - fetch_formatter(:summary_group).call(data) - else - fetch_formatter(data.event).call(data) - end - end - - def fetch_formatter(key) - context.fetch_formatter(key) + context.fetch_formatter(data.key).call(data) end end end diff --git a/lib/trace_viz/renderers/verbose_renderer.rb b/lib/trace_viz/renderers/verbose_renderer.rb index b186941..26dcf17 100644 --- a/lib/trace_viz/renderers/verbose_renderer.rb +++ b/lib/trace_viz/renderers/verbose_renderer.rb @@ -22,7 +22,7 @@ def traverse_data(data) end def format_for(trace_data) - context.fetch_formatter(trace_data.event).call(trace_data) + context.fetch_formatter(trace_data.key).call(trace_data) end end end diff --git a/lib/trace_viz/trace_data/base.rb b/lib/trace_viz/trace_data/base.rb index 4fe91d8..abd995c 100644 --- a/lib/trace_viz/trace_data/base.rb +++ b/lib/trace_viz/trace_data/base.rb @@ -15,6 +15,13 @@ def initialize record_timestamp end + # + # Represents trace data type code + # + def key + raise NotImplementedError + end + def event raise NotImplementedError end diff --git a/lib/trace_viz/trace_data/summary_node.rb b/lib/trace_viz/trace_data/summary_node.rb index 3395fdd..b983264 100644 --- a/lib/trace_viz/trace_data/summary_node.rb +++ b/lib/trace_viz/trace_data/summary_node.rb @@ -27,6 +27,10 @@ def initialize(group:) add_children(representative_node.children) end + def key + :summary_group + end + def average_duration return 0 if count.zero? diff --git a/lib/trace_viz/trace_data/trace_point/base.rb b/lib/trace_viz/trace_data/trace_point/base.rb index 7cdde3f..44794fc 100644 --- a/lib/trace_viz/trace_data/trace_point/base.rb +++ b/lib/trace_viz/trace_data/trace_point/base.rb @@ -26,6 +26,10 @@ def initialize(trace_point) assign_ids end + def key + event + end + def duration 0 end diff --git a/lib/trace_viz/transformers/base_transformer.rb b/lib/trace_viz/transformers/base_transformer.rb index e78b6f1..5b0ab51 100644 --- a/lib/trace_viz/transformers/base_transformer.rb +++ b/lib/trace_viz/transformers/base_transformer.rb @@ -4,6 +4,8 @@ module TraceViz module Transformers + TransformedNode = Struct.new(:data, :children) + class BaseTransformer include Helpers::ConfigHelper diff --git a/lib/trace_viz/transformers/summary_transformer.rb b/lib/trace_viz/transformers/summary_transformer.rb index c075f99..a54adef 100644 --- a/lib/trace_viz/transformers/summary_transformer.rb +++ b/lib/trace_viz/transformers/summary_transformer.rb @@ -7,9 +7,12 @@ module TraceViz module Transformers class SummaryTransformer < BaseTransformer def transform - return [] unless valid_children?(root_node) + return root_node unless valid_children?(root_node) - transform_nodes(root_node.children) + TransformedNode.new( + root_node, + transform_nodes(root_node.children), + ) end private @@ -46,17 +49,17 @@ def transform_group(group) def create_summary_node(group) summary_node = TraceData::SummaryNode.new(group: group) - { - data: summary_node, - children: transform_nodes(summary_node.children || []), - } + TransformedNode.new( + summary_node, + transform_nodes(summary_node.children), + ) end def create_single_node(node) - { - data: node, - children: transform_nodes(node.children || []), - } + TransformedNode.new( + node, + transform_nodes(node.children), + ) end def group_keys diff --git a/lib/trace_viz/utils/hierarchy_flattener.rb b/lib/trace_viz/utils/hierarchy_flattener.rb new file mode 100644 index 0000000..3a5e64e --- /dev/null +++ b/lib/trace_viz/utils/hierarchy_flattener.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module TraceViz + module Utils + class HierarchyFlattener + class << self + def flatten(root) + result = [] + traverse(root) do |node| + result << node + end + result + end + + private + + # Make sure already have .children method + def traverse(node, &block) + yield node + node.children.each do |child| + traverse(child, &block) + end + end + end + end + end +end From 050bc9ab76761161d6d7ea5e238ee61667ee1a63 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Sat, 11 Jan 2025 15:37:39 +0700 Subject: [PATCH 02/11] Refactor exporter and logger classes to utilize RendererBuilder for improved rendering logic; add Mermaid sequence renderer and syntax support --- lib/trace_viz/exporters/base_exporter.rb | 10 ++++- lib/trace_viz/exporters/registry.rb | 1 + lib/trace_viz/exporters/text_exporter.rb | 18 -------- .../loggers/post_collection_logger.rb | 9 ++-- lib/trace_viz/loggers/trace_logger.rb | 6 +-- lib/trace_viz/renderers/base_renderer.rb | 2 +- .../renderers/mermaid/sequence_renderer.rb | 42 +++++++++++++++++++ .../mermaid/syntax/sequence_syntax.rb | 27 ++++++++++++ lib/trace_viz/renderers/renderer_builder.rb | 20 +++++++++ lib/trace_viz/renderers/renderer_factory.rb | 21 +++++----- lib/trace_viz/shared/renderer_helper.rb | 16 ------- 11 files changed, 116 insertions(+), 56 deletions(-) create mode 100644 lib/trace_viz/renderers/mermaid/sequence_renderer.rb create mode 100644 lib/trace_viz/renderers/mermaid/syntax/sequence_syntax.rb create mode 100644 lib/trace_viz/renderers/renderer_builder.rb diff --git a/lib/trace_viz/exporters/base_exporter.rb b/lib/trace_viz/exporters/base_exporter.rb index a83184e..6b90296 100644 --- a/lib/trace_viz/exporters/base_exporter.rb +++ b/lib/trace_viz/exporters/base_exporter.rb @@ -3,6 +3,8 @@ require "fileutils" require "trace_viz/helpers" require "trace_viz/shared" +require "trace_viz/renderers/renderer_builder" +require "trace_viz/formatters/export/formatter_factory" module TraceViz module Exporters @@ -15,6 +17,12 @@ def initialize(collector) @logger = config.logger @collector = collector + + @renderer = Renderers::RendererBuilder.build( + collector, + key: fetch_general_config(:mode), + formatter_factory: Formatters::Export::FormatterFactory.new, + ) end def export @@ -34,7 +42,7 @@ def export private - attr_reader :export_config, :logger, :collector + attr_reader :export_config, :logger, :collector, :renderer def content raise NotImplementedError diff --git a/lib/trace_viz/exporters/registry.rb b/lib/trace_viz/exporters/registry.rb index 31adcf8..ee5e611 100644 --- a/lib/trace_viz/exporters/registry.rb +++ b/lib/trace_viz/exporters/registry.rb @@ -7,6 +7,7 @@ module Exporters class Registry EXPORTS = { txt: TextExporter, + # mermaid: MermaidExporter, } class << self diff --git a/lib/trace_viz/exporters/text_exporter.rb b/lib/trace_viz/exporters/text_exporter.rb index 74e3776..d1d99e3 100644 --- a/lib/trace_viz/exporters/text_exporter.rb +++ b/lib/trace_viz/exporters/text_exporter.rb @@ -1,28 +1,10 @@ # frozen_string_literal: true require_relative "base_exporter" -require "trace_viz/renderers/renderer_factory" -require "trace_viz/renderers/render_context" -require "trace_viz/formatters/export/formatter_factory" module TraceViz module Exporters class TextExporter < BaseExporter - def initialize(collector) - super(collector) - - @formatter_factory = Formatters::Export::FormatterFactory.new - @renderer = build_renderer( - collector, - mode: fetch_general_config(:mode), - formatter_factory: @formatter_factory, - ) - end - - private - - attr_reader :renderer - def content data.join("\n") end diff --git a/lib/trace_viz/loggers/post_collection_logger.rb b/lib/trace_viz/loggers/post_collection_logger.rb index dcc3ddc..66cb64e 100644 --- a/lib/trace_viz/loggers/post_collection_logger.rb +++ b/lib/trace_viz/loggers/post_collection_logger.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "trace_viz/formatters/log/formatter_factory" +require "trace_viz/renderers/renderer_builder" require "trace_viz/shared" require_relative "base_logger" require_relative "log_level_resolver" @@ -15,11 +16,11 @@ def initialize(collector) super() @collector = collector - @formatter_factory = Formatters::Log::FormatterFactory.new - @renderer = build_renderer( + + @renderer = Renderers::RendererBuilder.build( collector, - mode: fetch_general_config(:mode), - formatter_factory: @formatter_factory, + key: fetch_general_config(:mode), + formatter_factory: Formatters::Log::FormatterFactory.new, ) end diff --git a/lib/trace_viz/loggers/trace_logger.rb b/lib/trace_viz/loggers/trace_logger.rb index cee4ffc..5edf9b1 100644 --- a/lib/trace_viz/loggers/trace_logger.rb +++ b/lib/trace_viz/loggers/trace_logger.rb @@ -27,11 +27,7 @@ def log_level end def formatted_message - fetch_formatter(trace_data).call(trace_data) - end - - def fetch_formatter(trace_data) - formatter_factory.fetch_formatter(trace_data.key) + formatter_factory.fetch_formatter(trace_data.key).call(trace_data) end end end diff --git a/lib/trace_viz/renderers/base_renderer.rb b/lib/trace_viz/renderers/base_renderer.rb index edac708..4dfae8c 100644 --- a/lib/trace_viz/renderers/base_renderer.rb +++ b/lib/trace_viz/renderers/base_renderer.rb @@ -5,7 +5,7 @@ module Renderers class BaseRenderer NodeLine = Struct.new(:data, :line) - def initialize(collector, context:) + def initialize(collector, context) @collector = collector @context = context end diff --git a/lib/trace_viz/renderers/mermaid/sequence_renderer.rb b/lib/trace_viz/renderers/mermaid/sequence_renderer.rb new file mode 100644 index 0000000..a7ce241 --- /dev/null +++ b/lib/trace_viz/renderers/mermaid/sequence_renderer.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require_relative "../base_renderer" +require_relative "syntax/sequence_syntax" +require "trace_viz/diagram_builders/mermaid/sequence_builder" + +module TraceViz + module Renderers + module Mermaid + class SequenceRenderer < BaseRenderer + def initialize(collector) + super(collector, context: nil) + @syntax = Syntax::SequenceSyntax.new + end + + def to_lines + lines = [NodeLine.new(nil, syntax.header)] + + participants = data[:participants] + participants.each do |klass, alias_name| + lines << NodeLine.new(nil, syntax.participant(alias_name, klass)) + end + + messages = data[:messages] + messages.each do |message| + lines << NodeLine.new(nil, syntax.message(message[:from], message[:to], message[:message])) + end + + lines + end + + private + + attr_reader :syntax + + def data + @data ||= DiagramBuilders::Mermaid::SequenceBuilder.new(collector).build + end + end + end + end +end diff --git a/lib/trace_viz/renderers/mermaid/syntax/sequence_syntax.rb b/lib/trace_viz/renderers/mermaid/syntax/sequence_syntax.rb new file mode 100644 index 0000000..377e3b1 --- /dev/null +++ b/lib/trace_viz/renderers/mermaid/syntax/sequence_syntax.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module TraceViz + module Renderers + module Mermaid + module Syntax + class SequenceSyntax + def header + "sequenceDiagram" + end + + def participant(alias_name, name) + "participant #{alias_name} as #{name}" + end + + def message(from, to, message) + "#{from} ->> #{to}: #{message}" + end + + def return_message(from, to, return_value) + "#{to} -->> #{from}: #{return_value}" + end + end + end + end + end +end diff --git a/lib/trace_viz/renderers/renderer_builder.rb b/lib/trace_viz/renderers/renderer_builder.rb new file mode 100644 index 0000000..65b1a19 --- /dev/null +++ b/lib/trace_viz/renderers/renderer_builder.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require_relative "render_context" +require_relative "renderer_factory" + +module TraceViz + module Renderers + class RendererBuilder + class << self + def build(collector, key:, formatter_factory:) + raise ArgumentError, "Renderer key must be provided" if key.nil? + + render_context = RenderContext.new(formatter_factory: formatter_factory) + renderer_factory = RendererFactory.new(collector, render_context) + renderer_factory.build(key) + end + end + end + end +end diff --git a/lib/trace_viz/renderers/renderer_factory.rb b/lib/trace_viz/renderers/renderer_factory.rb index f782372..33166ae 100644 --- a/lib/trace_viz/renderers/renderer_factory.rb +++ b/lib/trace_viz/renderers/renderer_factory.rb @@ -2,6 +2,7 @@ require_relative "verbose_renderer" require_relative "summary_renderer" +require_relative "mermaid/sequence_renderer" module TraceViz module Renderers @@ -9,21 +10,19 @@ class RendererFactory RENDERERS = { verbose: VerboseRenderer, summary: SummaryRenderer, + sequence_renderer: Mermaid::SequenceRenderer, }.freeze - class << self - def build(mode, collector, context:) - renderer_class = fetch_renderer_class(mode) - renderer_class.new(collector, context: context) - end + def initialize(collector, context) + @collector = collector + @context = context + end - private + def build(key) + renderer_class = RENDERERS[key] + raise ArgumentError, "Invalid renderer key: #{key}" unless renderer_class - def fetch_renderer_class(mode) - RENDERERS.fetch(mode) do - raise ArgumentError, "Unknown mode: #{mode}. Valid modes are: #{RENDERERS.keys.join(", ")}" - end - end + renderer_class.new(@collector, @context) end end end diff --git a/lib/trace_viz/shared/renderer_helper.rb b/lib/trace_viz/shared/renderer_helper.rb index 0c9cd11..0e4bf8f 100644 --- a/lib/trace_viz/shared/renderer_helper.rb +++ b/lib/trace_viz/shared/renderer_helper.rb @@ -1,24 +1,8 @@ # frozen_string_literal: true -require "trace_viz/renderers/renderer_factory" -require "trace_viz/renderers/render_context" -require "trace_viz/formatters/export/formatter_factory" -require "trace_viz/formatters/log/formatter_factory" - module TraceViz module Shared module RendererHelper - def build_renderer(collector, mode:, formatter_factory:) - raise ArgumentError, "Collector cannot be nil" unless collector - raise ArgumentError, "Mode cannot be nil" unless mode - raise ArgumentError, "FormatterFactory cannot be nil" unless formatter_factory - - context = Renderers::RenderContext.new( - formatter_factory: formatter_factory, - ) - Renderers::RendererFactory.build(mode, collector, context: context) - end - def process_lines(lines, &block) return [] unless lines.is_a?(Array) From 134db983eb78890e5c535095e0b85f1b3867d545 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Sun, 12 Jan 2025 14:14:19 +0700 Subject: [PATCH 03/11] Add Mermaid exporter and update rendering logic for sequence diagrams --- lib/trace_viz/defaults/config.rb | 2 +- lib/trace_viz/exporters/base_exporter.rb | 23 +++++++-- lib/trace_viz/exporters/mermaid_exporter.rb | 17 +++++++ lib/trace_viz/exporters/registry.rb | 3 +- lib/trace_viz/exporters/text_exporter.rb | 8 ++- .../renderers/mermaid/sequence_renderer.rb | 49 ++++++++++++++----- lib/trace_viz/renderers/renderer_factory.rb | 2 +- 7 files changed, 79 insertions(+), 25 deletions(-) create mode 100644 lib/trace_viz/exporters/mermaid_exporter.rb diff --git a/lib/trace_viz/defaults/config.rb b/lib/trace_viz/defaults/config.rb index 7436357..af0381d 100644 --- a/lib/trace_viz/defaults/config.rb +++ b/lib/trace_viz/defaults/config.rb @@ -77,7 +77,7 @@ def valid_param_mode?(mode) end def valid_export_formats - [:txt, :json, :yaml].freeze + [:txt, :json, :yaml, :mermaid].freeze end def valid_export_format?(format) diff --git a/lib/trace_viz/exporters/base_exporter.rb b/lib/trace_viz/exporters/base_exporter.rb index 6b90296..0a19a6a 100644 --- a/lib/trace_viz/exporters/base_exporter.rb +++ b/lib/trace_viz/exporters/base_exporter.rb @@ -20,7 +20,7 @@ def initialize(collector) @renderer = Renderers::RendererBuilder.build( collector, - key: fetch_general_config(:mode), + key: renderer_mode, formatter_factory: Formatters::Export::FormatterFactory.new, ) end @@ -44,8 +44,16 @@ def export attr_reader :export_config, :logger, :collector, :renderer + def renderer_mode + fetch_general_config(:mode) + end + def content - raise NotImplementedError + data.join("\n") + end + + def data + process_lines(renderer.to_lines) { |line| line[:line] } end def export_enabled? @@ -75,10 +83,15 @@ def handle_empty_content end def file_path - format = export_config[:format] - path = export_config[:path] + "#{export_directory}/trace_output#{file_extension}" + end + + def export_directory + export_config[:path] + end - "#{path}/trace_output.#{format}" + def file_extension + raise NotImplementedError end def write_file(data) diff --git a/lib/trace_viz/exporters/mermaid_exporter.rb b/lib/trace_viz/exporters/mermaid_exporter.rb new file mode 100644 index 0000000..94b5c38 --- /dev/null +++ b/lib/trace_viz/exporters/mermaid_exporter.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module TraceViz + module Exporters + class MermaidExporter < BaseExporter + private + + def renderer_mode + :sequence_diagram + end + + def file_extension + ".mmd" + end + end + end +end diff --git a/lib/trace_viz/exporters/registry.rb b/lib/trace_viz/exporters/registry.rb index ee5e611..e1a4906 100644 --- a/lib/trace_viz/exporters/registry.rb +++ b/lib/trace_viz/exporters/registry.rb @@ -1,13 +1,14 @@ # frozen_string_literal: true require_relative "text_exporter" +require_relative "mermaid_exporter" module TraceViz module Exporters class Registry EXPORTS = { txt: TextExporter, - # mermaid: MermaidExporter, + mermaid: MermaidExporter, } class << self diff --git a/lib/trace_viz/exporters/text_exporter.rb b/lib/trace_viz/exporters/text_exporter.rb index d1d99e3..5b1399c 100644 --- a/lib/trace_viz/exporters/text_exporter.rb +++ b/lib/trace_viz/exporters/text_exporter.rb @@ -5,12 +5,10 @@ module TraceViz module Exporters class TextExporter < BaseExporter - def content - data.join("\n") - end + private - def data - process_lines(renderer.to_lines) { |line| line[:line] } + def file_extension + ".txt" end end end diff --git a/lib/trace_viz/renderers/mermaid/sequence_renderer.rb b/lib/trace_viz/renderers/mermaid/sequence_renderer.rb index a7ce241..fdfe60f 100644 --- a/lib/trace_viz/renderers/mermaid/sequence_renderer.rb +++ b/lib/trace_viz/renderers/mermaid/sequence_renderer.rb @@ -8,23 +8,17 @@ module TraceViz module Renderers module Mermaid class SequenceRenderer < BaseRenderer - def initialize(collector) - super(collector, context: nil) + def initialize(collector, context) + super @syntax = Syntax::SequenceSyntax.new end def to_lines - lines = [NodeLine.new(nil, syntax.header)] + lines = [] - participants = data[:participants] - participants.each do |klass, alias_name| - lines << NodeLine.new(nil, syntax.participant(alias_name, klass)) - end - - messages = data[:messages] - messages.each do |message| - lines << NodeLine.new(nil, syntax.message(message[:from], message[:to], message[:message])) - end + lines.push(header_line) + lines.concat(participant_lines) + lines.concat(message_lines) lines end @@ -36,6 +30,37 @@ def to_lines def data @data ||= DiagramBuilders::Mermaid::SequenceBuilder.new(collector).build end + + def header_line + NodeLine.new(nil, syntax.header) + end + + def participant_lines + participants = data[:participants] + participants.map do |klass, alias_name| + NodeLine.new(nil, build_participant_line(alias_name, klass)) + end + end + + def message_lines + messages = data[:messages] + messages.map do |message| + NodeLine.new(nil, build_message_line(message)) + end + end + + def build_participant_line(alias_name, klass) + "#{indent_level}#{syntax.participant(alias_name, klass)}" + end + + def build_message_line(message) + "#{indent_level}#{syntax.message(message[:from], message[:to], message[:message])}" + end + + def indent_level + tab_size = 2 + " " * tab_size + end end end end diff --git a/lib/trace_viz/renderers/renderer_factory.rb b/lib/trace_viz/renderers/renderer_factory.rb index 33166ae..f53d7ec 100644 --- a/lib/trace_viz/renderers/renderer_factory.rb +++ b/lib/trace_viz/renderers/renderer_factory.rb @@ -10,7 +10,7 @@ class RendererFactory RENDERERS = { verbose: VerboseRenderer, summary: SummaryRenderer, - sequence_renderer: Mermaid::SequenceRenderer, + sequence_diagram: Mermaid::SequenceRenderer, }.freeze def initialize(collector, context) From a733aca38456a4ee8f981cc302bba7b4413c3bd5 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Sun, 12 Jan 2025 17:37:12 +0700 Subject: [PATCH 04/11] Refactor Mermaid diagram builders and renderers; introduce SequenceData, SequenceSyntax, SequenceProcessor, and SequenceConverter for improved structure and functionality --- .../diagram_builders/base_builder.rb | 17 --- .../extractors/message_extractor.rb | 113 +++++++++++++++--- .../diagram_builders/mermaid/base_builder.rb | 12 -- .../mermaid/sequence_builder.rb | 26 ++-- .../mermaid/sequence_converter.rb | 55 +++++++++ .../diagram_builders/mermaid/sequence_data.rb | 17 +++ .../mermaid/sequence_processor.rb | 41 +++++++ .../mermaid/sequence_syntax.rb | 45 +++++++ .../renderers/mermaid/sequence_renderer.rb | 55 ++++----- .../mermaid/syntax/sequence_syntax.rb | 27 ----- .../trace_data/trace_point/method_call.rb | 4 + 11 files changed, 295 insertions(+), 117 deletions(-) delete mode 100644 lib/trace_viz/diagram_builders/base_builder.rb delete mode 100644 lib/trace_viz/diagram_builders/mermaid/base_builder.rb create mode 100644 lib/trace_viz/diagram_builders/mermaid/sequence_converter.rb create mode 100644 lib/trace_viz/diagram_builders/mermaid/sequence_data.rb create mode 100644 lib/trace_viz/diagram_builders/mermaid/sequence_processor.rb create mode 100644 lib/trace_viz/diagram_builders/mermaid/sequence_syntax.rb delete mode 100644 lib/trace_viz/renderers/mermaid/syntax/sequence_syntax.rb diff --git a/lib/trace_viz/diagram_builders/base_builder.rb b/lib/trace_viz/diagram_builders/base_builder.rb deleted file mode 100644 index 62e53ef..0000000 --- a/lib/trace_viz/diagram_builders/base_builder.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module TraceViz - module DiagramBuilders - class BaseBuilder - attr_reader :collector - - def initialize(collector) - @collector = collector - end - - def build - raise NotImplementedError - end - end - end -end diff --git a/lib/trace_viz/diagram_builders/extractors/message_extractor.rb b/lib/trace_viz/diagram_builders/extractors/message_extractor.rb index be99ba8..09a7202 100644 --- a/lib/trace_viz/diagram_builders/extractors/message_extractor.rb +++ b/lib/trace_viz/diagram_builders/extractors/message_extractor.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require "trace_viz/utils/hierarchy_flattener" -require_relative "base_extractor" - module TraceViz module DiagramBuilders module Extractors @@ -13,32 +10,120 @@ def initialize(collector, participants) end def extract - nodes_without_root.map { |node| build_message(node.data) } + root = data + + root.children.flat_map { |child| traverse(child, nil) } end private attr_reader :participants - def data - @data ||= Transformers::SummaryTransformer.new(collector).transform + def traverse(node, caller_trace) + messages = [] + trace = node.data + current_participant = participants[trace.klass] + + if caller_trace && participants[caller_trace.klass] != current_participant + messages << call_message(caller_trace, trace) + end + + messages << loop_start(trace) if loop?(trace) + messages << build_message(trace) + messages << activate(trace) if node_has_children?(trace) + + # Recursively process child nodes + node.children.each do |child| + messages.concat(traverse(child, trace)) + end + + messages << deactivate(trace) if node_has_children?(trace) + messages << loop_end(trace) if loop?(trace) + + if caller_trace && participants[caller_trace.klass] != current_participant + messages << return_message(trace, caller_trace) + end + + messages.compact end - def nodes - @nodes ||= Utils::HierarchyFlattener.flatten(data) + def loop_start(trace) + { type: :loop_start, message: "#{trace.count} calls" } + end + + def loop_end(trace) + { type: :loop_end } + end + + def activate(trace) + { type: :activate, participant: participants[trace.klass] } + end + + def deactivate(trace) + { type: :deactivate, participant: participants[trace.klass] } + end + + def build_message(trace) + { + type: :message, + from: participants[trace.klass], + to: participants[trace.klass], + message: trace.action, + } end - def nodes_without_root - nodes.drop(1) + def call_message(from_trace, to_trace) + { + type: :message, + from: participants[from_trace.klass], + to: participants[to_trace.klass], + message: "Calling #{to_trace.klass}#{format_params(to_trace.params)}", + } end - def build_message(data) + def return_message(from_trace, to_trace) { - from: participants[data.klass], - to: participants[data.klass], - message: data.action, + type: :return_message, + from: participants[from_trace.klass], + to: participants[to_trace.klass], + message: "Returning to #{to_trace.klass}#{format_result(from_trace.result)}", } end + + def format_params(params) + return "" if params.nil? || params.empty? + + summarized = params.map { |key, value| "#{sanitize(key)}: #{sanitize(value, 10)}" } + " [#{summarized.join(", ")}]" + end + + def format_result(result) + return "" if result.nil? + + " with result: #{sanitize(result, 15)}" + end + + def sanitize(value, max_length = 50) + str = value.to_s + sanitized = str.gsub(/[<>#&{}]/, "").gsub('"', "'") + sanitized.length > max_length ? "#{sanitized[0...max_length]}..." : sanitized + end + + def truncate(value, max_length) + sanitize(value, max_length) + end + + def loop?(trace) + trace.key == :summary_group + end + + def node_has_children?(trace) + trace.children.any? + end + + def data + @data ||= Transformers::SummaryTransformer.new(collector).transform + end end end end diff --git a/lib/trace_viz/diagram_builders/mermaid/base_builder.rb b/lib/trace_viz/diagram_builders/mermaid/base_builder.rb deleted file mode 100644 index c35c1d0..0000000 --- a/lib/trace_viz/diagram_builders/mermaid/base_builder.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require_relative "../base_builder" - -module TraceViz - module DiagramBuilders - module Mermaid - class BaseBuilder < DiagramBuilders::BaseBuilder - end - end - end -end diff --git a/lib/trace_viz/diagram_builders/mermaid/sequence_builder.rb b/lib/trace_viz/diagram_builders/mermaid/sequence_builder.rb index e18e13f..6534681 100644 --- a/lib/trace_viz/diagram_builders/mermaid/sequence_builder.rb +++ b/lib/trace_viz/diagram_builders/mermaid/sequence_builder.rb @@ -1,24 +1,22 @@ # frozen_string_literal: true -require_relative "base_builder" -require_relative "../extractors/participant_extractor" -require_relative "../extractors/message_extractor" - module TraceViz module DiagramBuilders module Mermaid - class SequenceBuilder < BaseBuilder - def build - participant_extractor = Extractors::ParticipantExtractor.new(collector) - participants = participant_extractor.extract + class SequenceBuilder + attr_reader :participants, :messages + + def initialize + @participants = {} + @messages = [] + end - message_extractor = Extractors::MessageExtractor.new(collector, participants) - messages = message_extractor.extract + def add_participant(alias_name, full_name) + @participants[full_name] = alias_name + end - { - participants: participants, - messages: messages, - } + def add_message(message) + @messages << message end end end diff --git a/lib/trace_viz/diagram_builders/mermaid/sequence_converter.rb b/lib/trace_viz/diagram_builders/mermaid/sequence_converter.rb new file mode 100644 index 0000000..0af4bd6 --- /dev/null +++ b/lib/trace_viz/diagram_builders/mermaid/sequence_converter.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require_relative "sequence_data" +require_relative "sequence_syntax" + +module TraceViz + module DiagramBuilders + module Mermaid + class SequenceConverter + def initialize + @syntax = SequenceSyntax.new(indent_level: 2) + end + + def convert(builder) + SequenceData.new( + header: syntax.header, + participants: convert_participants(builder.participants), + messages: convert_messages(builder.messages), + ) + end + + private + + attr_reader :syntax + + def convert_participants(participants) + participants.map do |klass, alias_name| + syntax.participant(alias_name, klass) + end + end + + def convert_messages(messages) + messages.map do |message| + case message[:type] + when :loop_start + syntax.loop_start(message[:message]) + when :loop_end + syntax.loop_end + when :activate + syntax.activate(message[:participant]) + when :deactivate + syntax.deactivate(message[:participant]) + when :message + syntax.message(message[:from], message[:to], message[:message]) + when :return_message + syntax.return_message(message[:from], message[:to], message[:message]) + else + raise "Unknown message type: #{message[:type]}" + end + end + end + end + end + end +end diff --git a/lib/trace_viz/diagram_builders/mermaid/sequence_data.rb b/lib/trace_viz/diagram_builders/mermaid/sequence_data.rb new file mode 100644 index 0000000..288b4f6 --- /dev/null +++ b/lib/trace_viz/diagram_builders/mermaid/sequence_data.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module TraceViz + module DiagramBuilders + module Mermaid + class SequenceData + attr_accessor :header, :participants, :messages + + def initialize(header: nil, participants: {}, messages: []) + @header = header + @participants = participants + @messages = messages + end + end + end + end +end diff --git a/lib/trace_viz/diagram_builders/mermaid/sequence_processor.rb b/lib/trace_viz/diagram_builders/mermaid/sequence_processor.rb new file mode 100644 index 0000000..0354560 --- /dev/null +++ b/lib/trace_viz/diagram_builders/mermaid/sequence_processor.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require_relative "../extractors/participant_extractor" +require_relative "../extractors/message_extractor" + +module TraceViz + module DiagramBuilders + module Mermaid + class SequenceProcessor + def initialize(builder, collector) + @builder = builder + @collector = collector + @participant_extractor = Extractors::ParticipantExtractor.new(collector) + @message_extractor = Extractors::MessageExtractor.new(collector, builder.participants) + end + + def process + process_participants + process_messages + builder + end + + private + + attr_reader :collector, :builder, :participant_extractor, :message_extractor + + def process_participants + participant_extractor.extract.each do |full_name, alias_name| + builder.add_participant(alias_name, full_name) + end + end + + def process_messages + message_extractor.extract.each do |message| + builder.add_message(message) + end + end + end + end + end +end diff --git a/lib/trace_viz/diagram_builders/mermaid/sequence_syntax.rb b/lib/trace_viz/diagram_builders/mermaid/sequence_syntax.rb new file mode 100644 index 0000000..fa1b622 --- /dev/null +++ b/lib/trace_viz/diagram_builders/mermaid/sequence_syntax.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module TraceViz + module DiagramBuilders + module Mermaid + class SequenceSyntax + def initialize(indent_level: 2) + @indent = " " * indent_level + end + + def header + "sequenceDiagram" + end + + def participant(alias_name, klass) + "#{@indent}participant #{alias_name} as #{klass}" + end + + def message(from, to, message) + "#{@indent}#{from} ->> #{to}: #{message}" + end + + def return_message(from, to, message) + "#{@indent}#{from} -->> #{to}: #{message}" + end + + def loop_start(message) + "#{@indent}loop #{message}" + end + + def loop_end + "#{@indent}end" + end + + def activate(participant) + "#{@indent}activate #{participant}" + end + + def deactivate(participant) + "#{@indent}deactivate #{participant}" + end + end + end + end +end diff --git a/lib/trace_viz/renderers/mermaid/sequence_renderer.rb b/lib/trace_viz/renderers/mermaid/sequence_renderer.rb index fdfe60f..bf53bb7 100644 --- a/lib/trace_viz/renderers/mermaid/sequence_renderer.rb +++ b/lib/trace_viz/renderers/mermaid/sequence_renderer.rb @@ -1,65 +1,54 @@ # frozen_string_literal: true require_relative "../base_renderer" -require_relative "syntax/sequence_syntax" +require "trace_viz/diagram_builders/mermaid/sequence_converter" require "trace_viz/diagram_builders/mermaid/sequence_builder" +require "trace_viz/diagram_builders/mermaid/sequence_processor" module TraceViz module Renderers module Mermaid class SequenceRenderer < BaseRenderer def initialize(collector, context) - super - @syntax = Syntax::SequenceSyntax.new + super(collector, context) + @builder = DiagramBuilders::Mermaid::SequenceBuilder.new + @processor = DiagramBuilders::Mermaid::SequenceProcessor.new(@builder, collector) + @converter = DiagramBuilders::Mermaid::SequenceConverter.new + + process_and_convert end def to_lines - lines = [] - - lines.push(header_line) - lines.concat(participant_lines) - lines.concat(message_lines) - - lines + [ + header_line, + *participant_lines, + *message_lines, + ] end private - attr_reader :syntax + attr_reader :builder, :processor, :converter - def data - @data ||= DiagramBuilders::Mermaid::SequenceBuilder.new(collector).build + def process_and_convert + @processed_data = processor.process + @converted_data = converter.convert(@processed_data) end def header_line - NodeLine.new(nil, syntax.header) + NodeLine.new(nil, @converted_data.header) end def participant_lines - participants = data[:participants] - participants.map do |klass, alias_name| - NodeLine.new(nil, build_participant_line(alias_name, klass)) - end + map_to_node_lines(@converted_data.participants) end def message_lines - messages = data[:messages] - messages.map do |message| - NodeLine.new(nil, build_message_line(message)) - end - end - - def build_participant_line(alias_name, klass) - "#{indent_level}#{syntax.participant(alias_name, klass)}" - end - - def build_message_line(message) - "#{indent_level}#{syntax.message(message[:from], message[:to], message[:message])}" + map_to_node_lines(@converted_data.messages) end - def indent_level - tab_size = 2 - " " * tab_size + def map_to_node_lines(data) + data.map { |item| NodeLine.new(nil, item) } end end end diff --git a/lib/trace_viz/renderers/mermaid/syntax/sequence_syntax.rb b/lib/trace_viz/renderers/mermaid/syntax/sequence_syntax.rb deleted file mode 100644 index 377e3b1..0000000 --- a/lib/trace_viz/renderers/mermaid/syntax/sequence_syntax.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module TraceViz - module Renderers - module Mermaid - module Syntax - class SequenceSyntax - def header - "sequenceDiagram" - end - - def participant(alias_name, name) - "participant #{alias_name} as #{name}" - end - - def message(from, to, message) - "#{from} ->> #{to}: #{message}" - end - - def return_message(from, to, return_value) - "#{to} -->> #{from}: #{return_value}" - end - end - end - end - end -end diff --git a/lib/trace_viz/trace_data/trace_point/method_call.rb b/lib/trace_viz/trace_data/trace_point/method_call.rb index 9a564bb..eb8b1ef 100644 --- a/lib/trace_viz/trace_data/trace_point/method_call.rb +++ b/lib/trace_viz/trace_data/trace_point/method_call.rb @@ -17,6 +17,10 @@ def initialize(trace_point) populate_params end + def result + method_return&.result + end + def link(method_return) @method_return = method_return end From 5dd9597825bc990f9a9895158f4c810445c46c79 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Sun, 12 Jan 2025 23:55:25 +0700 Subject: [PATCH 05/11] Implement base classes for builders, extractors, and models; introduce sequence diagram components for improved structure and functionality --- lib/trace_viz/builders/base_builder.rb | 11 +++ .../diagram/base_builder.rb} | 14 ++- .../builders/diagram/sequence_builder.rb | 26 +++++ .../extractors/participant_extractor.rb | 58 ----------- .../mermaid/sequence_builder.rb | 24 ----- .../mermaid/sequence_converter.rb | 55 ----------- .../diagram_builders/mermaid/sequence_data.rb | 17 ---- .../mermaid/sequence_processor.rb | 41 -------- .../mermaid/sequence_syntax.rb | 45 --------- lib/trace_viz/extractors/base_extractor.rb | 17 ++++ .../diagram}/message_extractor.rb | 98 +++++++++++++------ .../diagram/participant_extractor.rb | 60 ++++++++++++ .../formatters/diagram/sequence_formatter.rb | 25 +++++ lib/trace_viz/models/diagram.rb | 22 +++++ lib/trace_viz/models/message.rb | 16 +++ lib/trace_viz/models/participant.rb | 18 ++++ .../renderers/diagram/sequence_renderer.rb | 51 ++++++++++ .../renderers/mermaid/sequence_renderer.rb | 56 ----------- lib/trace_viz/renderers/renderer_factory.rb | 4 +- .../syntax/mermaid/sequence_syntax.rb | 52 ++++++++++ 20 files changed, 373 insertions(+), 337 deletions(-) create mode 100644 lib/trace_viz/builders/base_builder.rb rename lib/trace_viz/{diagram_builders/extractors/base_extractor.rb => builders/diagram/base_builder.rb} (57%) create mode 100644 lib/trace_viz/builders/diagram/sequence_builder.rb delete mode 100644 lib/trace_viz/diagram_builders/extractors/participant_extractor.rb delete mode 100644 lib/trace_viz/diagram_builders/mermaid/sequence_builder.rb delete mode 100644 lib/trace_viz/diagram_builders/mermaid/sequence_converter.rb delete mode 100644 lib/trace_viz/diagram_builders/mermaid/sequence_data.rb delete mode 100644 lib/trace_viz/diagram_builders/mermaid/sequence_processor.rb delete mode 100644 lib/trace_viz/diagram_builders/mermaid/sequence_syntax.rb create mode 100644 lib/trace_viz/extractors/base_extractor.rb rename lib/trace_viz/{diagram_builders/extractors => extractors/diagram}/message_extractor.rb (53%) create mode 100644 lib/trace_viz/extractors/diagram/participant_extractor.rb create mode 100644 lib/trace_viz/formatters/diagram/sequence_formatter.rb create mode 100644 lib/trace_viz/models/diagram.rb create mode 100644 lib/trace_viz/models/message.rb create mode 100644 lib/trace_viz/models/participant.rb create mode 100644 lib/trace_viz/renderers/diagram/sequence_renderer.rb delete mode 100644 lib/trace_viz/renderers/mermaid/sequence_renderer.rb create mode 100644 lib/trace_viz/syntax/mermaid/sequence_syntax.rb diff --git a/lib/trace_viz/builders/base_builder.rb b/lib/trace_viz/builders/base_builder.rb new file mode 100644 index 0000000..69f4b7a --- /dev/null +++ b/lib/trace_viz/builders/base_builder.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module TraceViz + module Builders + class BaseBuilder + def build + raise NotImplementedError + end + end + end +end diff --git a/lib/trace_viz/diagram_builders/extractors/base_extractor.rb b/lib/trace_viz/builders/diagram/base_builder.rb similarity index 57% rename from lib/trace_viz/diagram_builders/extractors/base_extractor.rb rename to lib/trace_viz/builders/diagram/base_builder.rb index 64dd4d3..cf47617 100644 --- a/lib/trace_viz/diagram_builders/extractors/base_extractor.rb +++ b/lib/trace_viz/builders/diagram/base_builder.rb @@ -1,18 +1,16 @@ # frozen_string_literal: true module TraceViz - module DiagramBuilders - module Extractors - class BaseExtractor - attr_reader :collector - + module Builders + module Diagram + class BaseBuilder def initialize(collector) @collector = collector end - def extract - raise NotImplementedError - end + private + + attr_reader :collector end end end diff --git a/lib/trace_viz/builders/diagram/sequence_builder.rb b/lib/trace_viz/builders/diagram/sequence_builder.rb new file mode 100644 index 0000000..48d78a8 --- /dev/null +++ b/lib/trace_viz/builders/diagram/sequence_builder.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "trace_viz/models/diagram" +require "trace_viz/extractors/diagram/participant_extractor" +require "trace_viz/extractors/diagram/message_extractor" +require_relative "base_builder" + +module TraceViz + module Builders + module Diagram + class SequenceBuilder < BaseBuilder + def build + diagram = Models::Diagram.new + + participants = Extractors::Diagram::ParticipantExtractor.new(collector).extract + messages = Extractors::Diagram::MessageExtractor.new(collector, participants).extract + + participants.each { |p| diagram.add_participant(p) } + messages.each { |m| diagram.add_message(m) } + + diagram + end + end + end + end +end diff --git a/lib/trace_viz/diagram_builders/extractors/participant_extractor.rb b/lib/trace_viz/diagram_builders/extractors/participant_extractor.rb deleted file mode 100644 index 8e85ffa..0000000 --- a/lib/trace_viz/diagram_builders/extractors/participant_extractor.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -require_relative "base_extractor" - -module TraceViz - module DiagramBuilders - module Extractors - class ParticipantExtractor < BaseExtractor - def extract - participants = [] - data.map do |node| - participants << node.klass - end - - generate_aliases(participants.uniq) - end - - private - - def data - collector.collection - end - - def generate_aliases(participants) - aliases = {} - participants.each do |participant| - aliases[participant] = generate_alias(participant, aliases) - end - aliases - end - - def generate_alias(klass, existing_aliases = {}) - # Convert class to string - klass_name = klass.to_s - - # Split into namespace components - parts = klass_name.split("::") - - # Extract uppercase letters from each part - initials = parts.map { |part| part.scan(/[A-Z]/).join } - - # Join initials with `_` to preserve hierarchy - alias_candidate = initials.join("_") - - # Ensure uniqueness - unique_alias = alias_candidate - counter = 1 - while existing_aliases.value?(unique_alias) - unique_alias = "#{alias_candidate}_#{counter}" - counter += 1 - end - - unique_alias - end - end - end - end -end diff --git a/lib/trace_viz/diagram_builders/mermaid/sequence_builder.rb b/lib/trace_viz/diagram_builders/mermaid/sequence_builder.rb deleted file mode 100644 index 6534681..0000000 --- a/lib/trace_viz/diagram_builders/mermaid/sequence_builder.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module TraceViz - module DiagramBuilders - module Mermaid - class SequenceBuilder - attr_reader :participants, :messages - - def initialize - @participants = {} - @messages = [] - end - - def add_participant(alias_name, full_name) - @participants[full_name] = alias_name - end - - def add_message(message) - @messages << message - end - end - end - end -end diff --git a/lib/trace_viz/diagram_builders/mermaid/sequence_converter.rb b/lib/trace_viz/diagram_builders/mermaid/sequence_converter.rb deleted file mode 100644 index 0af4bd6..0000000 --- a/lib/trace_viz/diagram_builders/mermaid/sequence_converter.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -require_relative "sequence_data" -require_relative "sequence_syntax" - -module TraceViz - module DiagramBuilders - module Mermaid - class SequenceConverter - def initialize - @syntax = SequenceSyntax.new(indent_level: 2) - end - - def convert(builder) - SequenceData.new( - header: syntax.header, - participants: convert_participants(builder.participants), - messages: convert_messages(builder.messages), - ) - end - - private - - attr_reader :syntax - - def convert_participants(participants) - participants.map do |klass, alias_name| - syntax.participant(alias_name, klass) - end - end - - def convert_messages(messages) - messages.map do |message| - case message[:type] - when :loop_start - syntax.loop_start(message[:message]) - when :loop_end - syntax.loop_end - when :activate - syntax.activate(message[:participant]) - when :deactivate - syntax.deactivate(message[:participant]) - when :message - syntax.message(message[:from], message[:to], message[:message]) - when :return_message - syntax.return_message(message[:from], message[:to], message[:message]) - else - raise "Unknown message type: #{message[:type]}" - end - end - end - end - end - end -end diff --git a/lib/trace_viz/diagram_builders/mermaid/sequence_data.rb b/lib/trace_viz/diagram_builders/mermaid/sequence_data.rb deleted file mode 100644 index 288b4f6..0000000 --- a/lib/trace_viz/diagram_builders/mermaid/sequence_data.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module TraceViz - module DiagramBuilders - module Mermaid - class SequenceData - attr_accessor :header, :participants, :messages - - def initialize(header: nil, participants: {}, messages: []) - @header = header - @participants = participants - @messages = messages - end - end - end - end -end diff --git a/lib/trace_viz/diagram_builders/mermaid/sequence_processor.rb b/lib/trace_viz/diagram_builders/mermaid/sequence_processor.rb deleted file mode 100644 index 0354560..0000000 --- a/lib/trace_viz/diagram_builders/mermaid/sequence_processor.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -require_relative "../extractors/participant_extractor" -require_relative "../extractors/message_extractor" - -module TraceViz - module DiagramBuilders - module Mermaid - class SequenceProcessor - def initialize(builder, collector) - @builder = builder - @collector = collector - @participant_extractor = Extractors::ParticipantExtractor.new(collector) - @message_extractor = Extractors::MessageExtractor.new(collector, builder.participants) - end - - def process - process_participants - process_messages - builder - end - - private - - attr_reader :collector, :builder, :participant_extractor, :message_extractor - - def process_participants - participant_extractor.extract.each do |full_name, alias_name| - builder.add_participant(alias_name, full_name) - end - end - - def process_messages - message_extractor.extract.each do |message| - builder.add_message(message) - end - end - end - end - end -end diff --git a/lib/trace_viz/diagram_builders/mermaid/sequence_syntax.rb b/lib/trace_viz/diagram_builders/mermaid/sequence_syntax.rb deleted file mode 100644 index fa1b622..0000000 --- a/lib/trace_viz/diagram_builders/mermaid/sequence_syntax.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module TraceViz - module DiagramBuilders - module Mermaid - class SequenceSyntax - def initialize(indent_level: 2) - @indent = " " * indent_level - end - - def header - "sequenceDiagram" - end - - def participant(alias_name, klass) - "#{@indent}participant #{alias_name} as #{klass}" - end - - def message(from, to, message) - "#{@indent}#{from} ->> #{to}: #{message}" - end - - def return_message(from, to, message) - "#{@indent}#{from} -->> #{to}: #{message}" - end - - def loop_start(message) - "#{@indent}loop #{message}" - end - - def loop_end - "#{@indent}end" - end - - def activate(participant) - "#{@indent}activate #{participant}" - end - - def deactivate(participant) - "#{@indent}deactivate #{participant}" - end - end - end - end -end diff --git a/lib/trace_viz/extractors/base_extractor.rb b/lib/trace_viz/extractors/base_extractor.rb new file mode 100644 index 0000000..730f397 --- /dev/null +++ b/lib/trace_viz/extractors/base_extractor.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module TraceViz + module Extractors + class BaseExtractor + attr_reader :collector + + def initialize(collector) + @collector = collector + end + + def extract + raise NotImplementedError + end + end + end +end diff --git a/lib/trace_viz/diagram_builders/extractors/message_extractor.rb b/lib/trace_viz/extractors/diagram/message_extractor.rb similarity index 53% rename from lib/trace_viz/diagram_builders/extractors/message_extractor.rb rename to lib/trace_viz/extractors/diagram/message_extractor.rb index 09a7202..98fec06 100644 --- a/lib/trace_viz/diagram_builders/extractors/message_extractor.rb +++ b/lib/trace_viz/extractors/diagram/message_extractor.rb @@ -1,95 +1,129 @@ # frozen_string_literal: true +require "trace_viz/models/message" +require_relative "../base_extractor" + module TraceViz - module DiagramBuilders - module Extractors + module Extractors + module Diagram class MessageExtractor < BaseExtractor def initialize(collector, participants) super(collector) - @participants = participants + + @participants_map = participants.each_with_object({}) do |p, memo| + memo[p.name] = p + end end def extract root = data - root.children.flat_map { |child| traverse(child, nil) } end private - attr_reader :participants + def participant_for(name) + @participants_map[name.to_s] + end def traverse(node, caller_trace) messages = [] trace = node.data - current_participant = participants[trace.klass] - if caller_trace && participants[caller_trace.klass] != current_participant + current_participant = participant_for(trace.klass) + + if caller_trace && participant_for(caller_trace.klass) != current_participant messages << call_message(caller_trace, trace) end messages << loop_start(trace) if loop?(trace) + messages << build_message(trace) + messages << activate(trace) if node_has_children?(trace) - # Recursively process child nodes node.children.each do |child| messages.concat(traverse(child, trace)) end messages << deactivate(trace) if node_has_children?(trace) + messages << loop_end(trace) if loop?(trace) - if caller_trace && participants[caller_trace.klass] != current_participant + if caller_trace && participant_for(caller_trace.klass) != current_participant messages << return_message(trace, caller_trace) end messages.compact end + # -- Domain-level message-building methods -- + def loop_start(trace) - { type: :loop_start, message: "#{trace.count} calls" } + Models::Message.new( + type: :loop_start, + from: nil, + to: nil, + content: "#{trace.count} calls", + ) end def loop_end(trace) - { type: :loop_end } + Models::Message.new( + type: :loop_end, + from: nil, + to: nil, + content: "", + ) end def activate(trace) - { type: :activate, participant: participants[trace.klass] } + Models::Message.new( + type: :activate, + from: nil, + to: participant_for(trace.klass), + content: "", + ) end def deactivate(trace) - { type: :deactivate, participant: participants[trace.klass] } + Models::Message.new( + type: :deactivate, + from: nil, + to: participant_for(trace.klass), + content: "", + ) end def build_message(trace) - { - type: :message, - from: participants[trace.klass], - to: participants[trace.klass], - message: trace.action, - } + Models::Message.new( + type: :call, + from: participant_for(trace.klass), + to: participant_for(trace.klass), + content: trace.action, + ) end def call_message(from_trace, to_trace) - { - type: :message, - from: participants[from_trace.klass], - to: participants[to_trace.klass], - message: "Calling #{to_trace.klass}#{format_params(to_trace.params)}", - } + Models::Message.new( + type: :call, + from: participant_for(from_trace.klass), + to: participant_for(to_trace.klass), + content: "Calling #{to_trace.klass}#{format_params(to_trace.params)}", + ) end def return_message(from_trace, to_trace) - { - type: :return_message, - from: participants[from_trace.klass], - to: participants[to_trace.klass], - message: "Returning to #{to_trace.klass}#{format_result(from_trace.result)}", - } + Models::Message.new( + type: :return, + from: participant_for(from_trace.klass), + to: participant_for(to_trace.klass), + content: "Returning to #{to_trace.klass}#{format_result(from_trace.result)}", + ) end + # -- Utility for formatting parameters/results -- + def format_params(params) return "" if params.nil? || params.empty? @@ -113,6 +147,8 @@ def truncate(value, max_length) sanitize(value, max_length) end + # -- Helper methods for logic -- + def loop?(trace) trace.key == :summary_group end diff --git a/lib/trace_viz/extractors/diagram/participant_extractor.rb b/lib/trace_viz/extractors/diagram/participant_extractor.rb new file mode 100644 index 0000000..1217d3e --- /dev/null +++ b/lib/trace_viz/extractors/diagram/participant_extractor.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "trace_viz/models/participant" # Adjust path if needed +require_relative "../base_extractor" + +module TraceViz + module Extractors + module Diagram + class ParticipantExtractor < BaseExtractor + def extract + unique_names = data.map(&:klass).uniq + + assigned_aliases = {} + + unique_names.map do |raw_name| + alias_name = generate_alias(raw_name, assigned_aliases) + + Models::Participant.new( + name: raw_name.to_s, + alias_name: alias_name, + ) + end + end + + private + + def data + collector.collection + end + + # Generates a unique alias for a name and stores it in assigned_aliases + def generate_alias(name, assigned_aliases) + # Convert name to string in case it's not already + str_name = name.to_s + + # Split into namespace components + parts = str_name.split("::") + + # Extract uppercase letters from each component + initials = parts.map { |part| part.scan(/[A-Z]/).join } + + # Join initials with `_` to preserve hierarchy + alias_candidate = initials.join("_") + + # Ensure uniqueness by appending a counter if needed + unique_alias = alias_candidate + counter = 1 + while assigned_aliases.value?(unique_alias) + unique_alias = "#{alias_candidate}_#{counter}" + counter += 1 + end + + # Record the final alias in the map + assigned_aliases[name] = unique_alias + unique_alias + end + end + end + end +end diff --git a/lib/trace_viz/formatters/diagram/sequence_formatter.rb b/lib/trace_viz/formatters/diagram/sequence_formatter.rb new file mode 100644 index 0000000..13c25c8 --- /dev/null +++ b/lib/trace_viz/formatters/diagram/sequence_formatter.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module TraceViz + module Formatters + module Diagram + class SequenceFormatter + def initialize(indent_level: 2) + @indent = " " * indent_level + end + + def indentation + @indent + end + + def format_participant_name(name) + name + end + + def format_message_content(content) + content[0..50] + end + end + end + end +end diff --git a/lib/trace_viz/models/diagram.rb b/lib/trace_viz/models/diagram.rb new file mode 100644 index 0000000..240218a --- /dev/null +++ b/lib/trace_viz/models/diagram.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module TraceViz + module Models + class Diagram + attr_reader :participants, :messages + + def initialize + @participants = [] + @messages = [] + end + + def add_participant(participant) + @participants << participant unless @participants.include?(participant) + end + + def add_message(message) + @messages << message + end + end + end +end diff --git a/lib/trace_viz/models/message.rb b/lib/trace_viz/models/message.rb new file mode 100644 index 0000000..acb7482 --- /dev/null +++ b/lib/trace_viz/models/message.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module TraceViz + module Models + class Message + attr_reader :type, :from, :to, :content + + def initialize(type:, from:, to:, content:) + @type = type + @from = from + @to = to + @content = content + end + end + end +end diff --git a/lib/trace_viz/models/participant.rb b/lib/trace_viz/models/participant.rb new file mode 100644 index 0000000..99f2df9 --- /dev/null +++ b/lib/trace_viz/models/participant.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module TraceViz + module Models + class Participant + attr_accessor :name, :alias_name + + def initialize(name:, alias_name: nil) + @name = name + @alias_name = alias_name + end + + def ==(other) + name == other.name + end + end + end +end diff --git a/lib/trace_viz/renderers/diagram/sequence_renderer.rb b/lib/trace_viz/renderers/diagram/sequence_renderer.rb new file mode 100644 index 0000000..e3a8a42 --- /dev/null +++ b/lib/trace_viz/renderers/diagram/sequence_renderer.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require_relative "../base_renderer" +require "trace_viz/builders/diagram/sequence_builder" +require "trace_viz/syntax/mermaid/sequence_syntax" +require "trace_viz/formatters/diagram/sequence_formatter" + +module TraceViz + module Renderers + module Diagram + class SequenceRenderer < BaseRenderer + def initialize(collector, context) + super(collector, context) + + @builder = Builders::Diagram::SequenceBuilder.new(collector) + @formatter = Formatters::Diagram::SequenceFormatter.new + @syntax = Syntax::Mermaid::SequenceSyntax.new(formatter: @formatter) + @diagram = builder.build + end + + def to_lines + [ + header_line, + *participant_lines, + *message_lines, + ] + end + + private + + attr_reader :builder, :diagram, :syntax + + def header_line + NodeLine.new(nil, syntax.header) + end + + def participant_lines + diagram.participants.map do |participant| + NodeLine.new(nil, syntax.participant(participant)) + end + end + + def message_lines + diagram.messages.map do |message| + NodeLine.new(nil, syntax.message(message)) + end + end + end + end + end +end diff --git a/lib/trace_viz/renderers/mermaid/sequence_renderer.rb b/lib/trace_viz/renderers/mermaid/sequence_renderer.rb deleted file mode 100644 index bf53bb7..0000000 --- a/lib/trace_viz/renderers/mermaid/sequence_renderer.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -require_relative "../base_renderer" -require "trace_viz/diagram_builders/mermaid/sequence_converter" -require "trace_viz/diagram_builders/mermaid/sequence_builder" -require "trace_viz/diagram_builders/mermaid/sequence_processor" - -module TraceViz - module Renderers - module Mermaid - class SequenceRenderer < BaseRenderer - def initialize(collector, context) - super(collector, context) - @builder = DiagramBuilders::Mermaid::SequenceBuilder.new - @processor = DiagramBuilders::Mermaid::SequenceProcessor.new(@builder, collector) - @converter = DiagramBuilders::Mermaid::SequenceConverter.new - - process_and_convert - end - - def to_lines - [ - header_line, - *participant_lines, - *message_lines, - ] - end - - private - - attr_reader :builder, :processor, :converter - - def process_and_convert - @processed_data = processor.process - @converted_data = converter.convert(@processed_data) - end - - def header_line - NodeLine.new(nil, @converted_data.header) - end - - def participant_lines - map_to_node_lines(@converted_data.participants) - end - - def message_lines - map_to_node_lines(@converted_data.messages) - end - - def map_to_node_lines(data) - data.map { |item| NodeLine.new(nil, item) } - end - end - end - end -end diff --git a/lib/trace_viz/renderers/renderer_factory.rb b/lib/trace_viz/renderers/renderer_factory.rb index f53d7ec..04612bf 100644 --- a/lib/trace_viz/renderers/renderer_factory.rb +++ b/lib/trace_viz/renderers/renderer_factory.rb @@ -2,7 +2,7 @@ require_relative "verbose_renderer" require_relative "summary_renderer" -require_relative "mermaid/sequence_renderer" +require_relative "diagram/sequence_renderer" module TraceViz module Renderers @@ -10,7 +10,7 @@ class RendererFactory RENDERERS = { verbose: VerboseRenderer, summary: SummaryRenderer, - sequence_diagram: Mermaid::SequenceRenderer, + sequence_diagram: Diagram::SequenceRenderer, }.freeze def initialize(collector, context) diff --git a/lib/trace_viz/syntax/mermaid/sequence_syntax.rb b/lib/trace_viz/syntax/mermaid/sequence_syntax.rb new file mode 100644 index 0000000..ce57dd6 --- /dev/null +++ b/lib/trace_viz/syntax/mermaid/sequence_syntax.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module TraceViz + module Syntax + module Mermaid + class SequenceSyntax + def initialize(formatter:) + @formatter = formatter + end + + def header + "sequenceDiagram" + end + + def participant(participant) + alias_name = participant.alias_name + name = formatter.format_participant_name(participant.name) + "#{indent}participant #{alias_name} as #{name}" + end + + def message(message) + from = message.from + to = message.to + content = formatter.format_message_content(message.content) + + case message.type + when :call + "#{indent}#{from&.alias_name} ->> #{to&.alias_name}: #{content}" + when :return + "#{indent}#{from&.alias_name} -->> #{to&.alias_name}: #{content}" + when :activate + "#{indent}activate #{to&.alias_name}" + when :deactivate + "#{indent}deactivate #{to&.alias_name}" + when :loop_start + "#{indent}loop #{content}" + when :loop_end + "#{indent}end" + end + end + + private + + attr_reader :formatter + + def indent + @formatter.indentation + end + end + end + end +end From 1cadd5fbdd9c08c6f06d6e75339369a4370929a5 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 13 Jan 2025 10:14:52 +0700 Subject: [PATCH 06/11] Add diagram formatters and refactor participant alias generation for improved structure and functionality --- .../builders/diagram/base_builder.rb | 7 - .../builders/diagram/message_builder.rb | 70 +++++++++ .../builders/diagram/sequence_builder.rb | 9 ++ .../extractors/diagram/message_extractor.rb | 145 +++++------------- .../diagram/participant_extractor.rb | 35 +---- lib/trace_viz/formatters/base_formatter.rb | 1 + .../diagram/sequence/base_formatter.rb | 14 ++ .../diagram/sequence/message_formatter.rb | 21 +++ .../formatters/diagram/sequence_formatter.rb | 25 --- lib/trace_viz/formatters/diagram_formatter.rb | 10 ++ .../managers/diagram/participant_manager.rb | 23 +++ .../renderers/diagram/sequence_renderer.rb | 4 +- .../syntax/mermaid/sequence_syntax.rb | 14 +- lib/trace_viz/utils/alias_generator.rb | 58 +++++++ 14 files changed, 260 insertions(+), 176 deletions(-) create mode 100644 lib/trace_viz/builders/diagram/message_builder.rb create mode 100644 lib/trace_viz/formatters/diagram/sequence/base_formatter.rb create mode 100644 lib/trace_viz/formatters/diagram/sequence/message_formatter.rb delete mode 100644 lib/trace_viz/formatters/diagram/sequence_formatter.rb create mode 100644 lib/trace_viz/formatters/diagram_formatter.rb create mode 100644 lib/trace_viz/managers/diagram/participant_manager.rb create mode 100644 lib/trace_viz/utils/alias_generator.rb diff --git a/lib/trace_viz/builders/diagram/base_builder.rb b/lib/trace_viz/builders/diagram/base_builder.rb index cf47617..c982fba 100644 --- a/lib/trace_viz/builders/diagram/base_builder.rb +++ b/lib/trace_viz/builders/diagram/base_builder.rb @@ -4,13 +4,6 @@ module TraceViz module Builders module Diagram class BaseBuilder - def initialize(collector) - @collector = collector - end - - private - - attr_reader :collector end end end diff --git a/lib/trace_viz/builders/diagram/message_builder.rb b/lib/trace_viz/builders/diagram/message_builder.rb new file mode 100644 index 0000000..51584d8 --- /dev/null +++ b/lib/trace_viz/builders/diagram/message_builder.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require "trace_viz/managers/diagram/participant_manager" +require "trace_viz/models/message" +require_relative "base_builder" + +module TraceViz + module Builders + module Diagram + class MessageBuilder < BaseBuilder + def initialize(formatter, participants) + super() + @formatter = formatter + @participants_manager = Managers::Diagram::ParticipantsManager.new(participants) + end + + def build(type, from, to, content) + Models::Message.new(type: type, from: from, to: to, content: content) + end + + def build_call_message(from_trace, to_trace) + build( + :call, + participants_manager.find(from_trace.klass), + participants_manager.find(to_trace.klass), + formatter.format_call(to_trace), + ) + end + + def build_return_message(from_trace, to_trace) + build( + :return, + participants_manager.find(from_trace.klass), + participants_manager.find(to_trace.klass), + formatter.format_return(from_trace, to_trace), + ) + end + + def build_loop_start_message(trace) + build(:loop_start, nil, nil, "#{trace.count} calls") + end + + def build_loop_end_message + build(:loop_end, nil, nil, "") + end + + def build_activate_message(trace) + build(:activate, nil, participants_manager.find(trace.klass), "") + end + + def build_deactivate_message(trace) + build(:deactivate, nil, participants_manager.find(trace.klass), "") + end + + def build_internal_message(trace) + build( + :call, + participants_manager.find(trace.klass), + participants_manager.find(trace.klass), + trace.action, + ) + end + + private + + attr_reader :formatter, :participants_manager + end + end + end +end diff --git a/lib/trace_viz/builders/diagram/sequence_builder.rb b/lib/trace_viz/builders/diagram/sequence_builder.rb index 48d78a8..da0bc94 100644 --- a/lib/trace_viz/builders/diagram/sequence_builder.rb +++ b/lib/trace_viz/builders/diagram/sequence_builder.rb @@ -9,6 +9,11 @@ module TraceViz module Builders module Diagram class SequenceBuilder < BaseBuilder + def initialize(collector) + super() + @collector = collector + end + def build diagram = Models::Diagram.new @@ -20,6 +25,10 @@ def build diagram end + + private + + attr_reader :collector end end end diff --git a/lib/trace_viz/extractors/diagram/message_extractor.rb b/lib/trace_viz/extractors/diagram/message_extractor.rb index 98fec06..f8da347 100644 --- a/lib/trace_viz/extractors/diagram/message_extractor.rb +++ b/lib/trace_viz/extractors/diagram/message_extractor.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true -require "trace_viz/models/message" +require "trace_viz/transformers/summary_transformer" +require "trace_viz/formatters/diagram/sequence/message_formatter" +require "trace_viz/builders/diagram/message_builder" +require "trace_viz/managers/diagram/participant_manager" require_relative "../base_extractor" module TraceViz @@ -10,9 +13,9 @@ class MessageExtractor < BaseExtractor def initialize(collector, participants) super(collector) - @participants_map = participants.each_with_object({}) do |p, memo| - memo[p.name] = p - end + @formatter = Formatters::Diagram::Sequence::MessageFormatter.new + @message_builder = Builders::Diagram::MessageBuilder.new(@formatter, participants) + @participants_manager = Managers::Diagram::ParticipantsManager.new(participants) end def extract @@ -22,129 +25,63 @@ def extract private - def participant_for(name) - @participants_map[name.to_s] - end + attr_reader :message_builder, :participants_manager def traverse(node, caller_trace) - messages = [] trace = node.data + messages = [] - current_participant = participant_for(trace.klass) + # Handle inter-participant messages + messages << handle_call_message(caller_trace, trace) - if caller_trace && participant_for(caller_trace.klass) != current_participant - messages << call_message(caller_trace, trace) - end + # Handle loop structures + messages << message_builder.build_loop_start_message(trace) if loop?(trace) - messages << loop_start(trace) if loop?(trace) + # Internal message + messages << message_builder.build_internal_message(trace) - messages << build_message(trace) + # Activation of participant + messages << message_builder.build_activate_message(trace) if node_has_children?(trace) - messages << activate(trace) if node_has_children?(trace) + # Process child nodes + messages.concat(process_children(node, trace)) - node.children.each do |child| - messages.concat(traverse(child, trace)) - end + # Deactivation of participant + messages << message_builder.build_deactivate_message(trace) if node_has_children?(trace) - messages << deactivate(trace) if node_has_children?(trace) + # End loop structures + messages << message_builder.build_loop_end_message if loop?(trace) - messages << loop_end(trace) if loop?(trace) - - if caller_trace && participant_for(caller_trace.klass) != current_participant - messages << return_message(trace, caller_trace) - end + # Handle return messages + messages << handle_return_message(caller_trace, trace) messages.compact end - # -- Domain-level message-building methods -- - - def loop_start(trace) - Models::Message.new( - type: :loop_start, - from: nil, - to: nil, - content: "#{trace.count} calls", - ) - end - - def loop_end(trace) - Models::Message.new( - type: :loop_end, - from: nil, - to: nil, - content: "", - ) - end - - def activate(trace) - Models::Message.new( - type: :activate, - from: nil, - to: participant_for(trace.klass), - content: "", - ) - end - - def deactivate(trace) - Models::Message.new( - type: :deactivate, - from: nil, - to: participant_for(trace.klass), - content: "", - ) - end + def handle_call_message(caller_trace, current_trace) + return unless caller_trace - def build_message(trace) - Models::Message.new( - type: :call, - from: participant_for(trace.klass), - to: participant_for(trace.klass), - content: trace.action, - ) - end + from_participant = participants_manager.find(caller_trace.klass) + to_participant = participants_manager.find(current_trace.klass) - def call_message(from_trace, to_trace) - Models::Message.new( - type: :call, - from: participant_for(from_trace.klass), - to: participant_for(to_trace.klass), - content: "Calling #{to_trace.klass}#{format_params(to_trace.params)}", - ) - end - - def return_message(from_trace, to_trace) - Models::Message.new( - type: :return, - from: participant_for(from_trace.klass), - to: participant_for(to_trace.klass), - content: "Returning to #{to_trace.klass}#{format_result(from_trace.result)}", - ) - end - - # -- Utility for formatting parameters/results -- - - def format_params(params) - return "" if params.nil? || params.empty? - - summarized = params.map { |key, value| "#{sanitize(key)}: #{sanitize(value, 10)}" } - " [#{summarized.join(", ")}]" + if from_participant != to_participant + message_builder.build_call_message(caller_trace, current_trace) + end end - def format_result(result) - return "" if result.nil? + def handle_return_message(caller_trace, current_trace) + return unless caller_trace - " with result: #{sanitize(result, 15)}" - end + from_participant = participants_manager.find(caller_trace.klass) + to_participant = participants_manager.find(current_trace.klass) - def sanitize(value, max_length = 50) - str = value.to_s - sanitized = str.gsub(/[<>#&{}]/, "").gsub('"', "'") - sanitized.length > max_length ? "#{sanitized[0...max_length]}..." : sanitized + if from_participant != to_participant + message_builder.build_return_message(current_trace, caller_trace) + end end - def truncate(value, max_length) - sanitize(value, max_length) + def process_children(node, trace) + node.children.flat_map { |child| traverse(child, trace) } end # -- Helper methods for logic -- diff --git a/lib/trace_viz/extractors/diagram/participant_extractor.rb b/lib/trace_viz/extractors/diagram/participant_extractor.rb index 1217d3e..d07efd9 100644 --- a/lib/trace_viz/extractors/diagram/participant_extractor.rb +++ b/lib/trace_viz/extractors/diagram/participant_extractor.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -require "trace_viz/models/participant" # Adjust path if needed +require "trace_viz/utils/alias_generator" +require "trace_viz/models/participant" require_relative "../base_extractor" module TraceViz @@ -13,7 +14,10 @@ def extract assigned_aliases = {} unique_names.map do |raw_name| - alias_name = generate_alias(raw_name, assigned_aliases) + alias_name = Utils::AliasGenerator.generate( + name: raw_name, + assigned_aliases: assigned_aliases, + ) Models::Participant.new( name: raw_name.to_s, @@ -27,33 +31,6 @@ def extract def data collector.collection end - - # Generates a unique alias for a name and stores it in assigned_aliases - def generate_alias(name, assigned_aliases) - # Convert name to string in case it's not already - str_name = name.to_s - - # Split into namespace components - parts = str_name.split("::") - - # Extract uppercase letters from each component - initials = parts.map { |part| part.scan(/[A-Z]/).join } - - # Join initials with `_` to preserve hierarchy - alias_candidate = initials.join("_") - - # Ensure uniqueness by appending a counter if needed - unique_alias = alias_candidate - counter = 1 - while assigned_aliases.value?(unique_alias) - unique_alias = "#{alias_candidate}_#{counter}" - counter += 1 - end - - # Record the final alias in the map - assigned_aliases[name] = unique_alias - unique_alias - end end end end diff --git a/lib/trace_viz/formatters/base_formatter.rb b/lib/trace_viz/formatters/base_formatter.rb index c2830a6..2aa7fb0 100644 --- a/lib/trace_viz/formatters/base_formatter.rb +++ b/lib/trace_viz/formatters/base_formatter.rb @@ -7,6 +7,7 @@ module Formatters class BaseFormatter include Helpers::ConfigHelper + # Format the data to a line def call raise NotImplementedError end diff --git a/lib/trace_viz/formatters/diagram/sequence/base_formatter.rb b/lib/trace_viz/formatters/diagram/sequence/base_formatter.rb new file mode 100644 index 0000000..881e7fa --- /dev/null +++ b/lib/trace_viz/formatters/diagram/sequence/base_formatter.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative "../../diagram_formatter" + +module TraceViz + module Formatters + module Diagram + module Sequence + class BaseFormatter < Formatters::DiagramFormatter + end + end + end + end +end diff --git a/lib/trace_viz/formatters/diagram/sequence/message_formatter.rb b/lib/trace_viz/formatters/diagram/sequence/message_formatter.rb new file mode 100644 index 0000000..fd4a49c --- /dev/null +++ b/lib/trace_viz/formatters/diagram/sequence/message_formatter.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative "base_formatter" + +module TraceViz + module Formatters + module Diagram + module Sequence + class MessageFormatter < BaseFormatter + def format_call(to_trace) + "Calling #{to_trace.klass}" + end + + def format_return(from_trace, to_trace) + "Returning to #{to_trace.klass}" + end + end + end + end + end +end diff --git a/lib/trace_viz/formatters/diagram/sequence_formatter.rb b/lib/trace_viz/formatters/diagram/sequence_formatter.rb deleted file mode 100644 index 13c25c8..0000000 --- a/lib/trace_viz/formatters/diagram/sequence_formatter.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module TraceViz - module Formatters - module Diagram - class SequenceFormatter - def initialize(indent_level: 2) - @indent = " " * indent_level - end - - def indentation - @indent - end - - def format_participant_name(name) - name - end - - def format_message_content(content) - content[0..50] - end - end - end - end -end diff --git a/lib/trace_viz/formatters/diagram_formatter.rb b/lib/trace_viz/formatters/diagram_formatter.rb new file mode 100644 index 0000000..bec787e --- /dev/null +++ b/lib/trace_viz/formatters/diagram_formatter.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "base_formatter" + +module TraceViz + module Formatters + class DiagramFormatter < BaseFormatter + end + end +end diff --git a/lib/trace_viz/managers/diagram/participant_manager.rb b/lib/trace_viz/managers/diagram/participant_manager.rb new file mode 100644 index 0000000..53e7d81 --- /dev/null +++ b/lib/trace_viz/managers/diagram/participant_manager.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module TraceViz + module Managers + module Diagram + class ParticipantsManager + def initialize(participants) + @participants_map = build_participants_map(participants) + end + + def find(name) + @participants_map[name.to_s] + end + + private + + def build_participants_map(participants) + participants.each_with_object({}) { |participant, memo| memo[participant.name] = participant } + end + end + end + end +end diff --git a/lib/trace_viz/renderers/diagram/sequence_renderer.rb b/lib/trace_viz/renderers/diagram/sequence_renderer.rb index e3a8a42..c57a9ec 100644 --- a/lib/trace_viz/renderers/diagram/sequence_renderer.rb +++ b/lib/trace_viz/renderers/diagram/sequence_renderer.rb @@ -3,7 +3,6 @@ require_relative "../base_renderer" require "trace_viz/builders/diagram/sequence_builder" require "trace_viz/syntax/mermaid/sequence_syntax" -require "trace_viz/formatters/diagram/sequence_formatter" module TraceViz module Renderers @@ -13,8 +12,7 @@ def initialize(collector, context) super(collector, context) @builder = Builders::Diagram::SequenceBuilder.new(collector) - @formatter = Formatters::Diagram::SequenceFormatter.new - @syntax = Syntax::Mermaid::SequenceSyntax.new(formatter: @formatter) + @syntax = Syntax::Mermaid::SequenceSyntax.new @diagram = builder.build end diff --git a/lib/trace_viz/syntax/mermaid/sequence_syntax.rb b/lib/trace_viz/syntax/mermaid/sequence_syntax.rb index ce57dd6..298b8c3 100644 --- a/lib/trace_viz/syntax/mermaid/sequence_syntax.rb +++ b/lib/trace_viz/syntax/mermaid/sequence_syntax.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true +require "trace_viz/helpers" + module TraceViz module Syntax module Mermaid class SequenceSyntax - def initialize(formatter:) - @formatter = formatter - end + include Helpers::ConfigHelper def header "sequenceDiagram" @@ -14,14 +14,14 @@ def header def participant(participant) alias_name = participant.alias_name - name = formatter.format_participant_name(participant.name) + name = participant.name "#{indent}participant #{alias_name} as #{name}" end def message(message) from = message.from to = message.to - content = formatter.format_message_content(message.content) + content = message.content case message.type when :call @@ -41,10 +41,8 @@ def message(message) private - attr_reader :formatter - def indent - @formatter.indentation + " " * fetch_general_config(:tab_size) end end end diff --git a/lib/trace_viz/utils/alias_generator.rb b/lib/trace_viz/utils/alias_generator.rb new file mode 100644 index 0000000..0baad7e --- /dev/null +++ b/lib/trace_viz/utils/alias_generator.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module TraceViz + module Utils + class AliasGenerator + COMPONENT_DELIMITER = "::" + ALIAS_DELIMITER = "_" + + class << self + # + # Generates a unique alias for a given name and ensures it doesn't conflict + # with existing aliases. + # + def generate(name:, assigned_aliases:) + raise ArgumentError, "name cannot be nil" if name.nil? + + # Break the name into components and extract initials + alias_candidate = extract_initials(name.to_s) + + # Ensure the alias is unique + unique_alias = ensure_unique_alias( + candidate: alias_candidate, + assigned_aliases: assigned_aliases, + original_name: name, + ) + + # Record the alias in the map + assigned_aliases[name] = unique_alias + unique_alias + end + + private + + # + # Extracts initials from a namespaced string + # + def extract_initials(full_name) + parts = full_name.split(COMPONENT_DELIMITER) + initials = parts.map { |part| part.scan(/[A-Z]/).join } + initials.join(ALIAS_DELIMITER) + end + + # + # Ensures the alias is unique by appending a counter if necessary + # + def ensure_unique_alias(candidate:, assigned_aliases:, original_name:) + unique_alias = candidate + counter = 1 + while assigned_aliases.value?(unique_alias) + unique_alias = "#{candidate}#{ALIAS_DELIMITER}#{counter}" + counter += 1 + end + unique_alias + end + end + end + end +end From 5579c294cc84a86ebba2e4c3946444960112ea74 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 13 Jan 2025 11:38:58 +0700 Subject: [PATCH 07/11] Refactor message formatting and sanitization in sequence diagram syntax for improved readability and consistency --- .../diagram/sequence/message_formatter.rb | 4 +- .../syntax/mermaid/sequence_syntax.rb | 37 ++++++++++++++----- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/lib/trace_viz/formatters/diagram/sequence/message_formatter.rb b/lib/trace_viz/formatters/diagram/sequence/message_formatter.rb index fd4a49c..3860e4f 100644 --- a/lib/trace_viz/formatters/diagram/sequence/message_formatter.rb +++ b/lib/trace_viz/formatters/diagram/sequence/message_formatter.rb @@ -8,11 +8,11 @@ module Diagram module Sequence class MessageFormatter < BaseFormatter def format_call(to_trace) - "Calling #{to_trace.klass}" + "Calling" end def format_return(from_trace, to_trace) - "Returning to #{to_trace.klass}" + "Returning" end end end diff --git a/lib/trace_viz/syntax/mermaid/sequence_syntax.rb b/lib/trace_viz/syntax/mermaid/sequence_syntax.rb index 298b8c3..ac3a608 100644 --- a/lib/trace_viz/syntax/mermaid/sequence_syntax.rb +++ b/lib/trace_viz/syntax/mermaid/sequence_syntax.rb @@ -13,25 +13,25 @@ def header end def participant(participant) - alias_name = participant.alias_name - name = participant.name + alias_name = sanitize_name(participant.alias_name) + name = sanitize_name(participant.name) "#{indent}participant #{alias_name} as #{name}" end def message(message) - from = message.from - to = message.to - content = message.content + from = sanitize_name(message.from&.alias_name) + to = sanitize_name(message.to&.alias_name) + content = sanitize_name(message.content) case message.type when :call - "#{indent}#{from&.alias_name} ->> #{to&.alias_name}: #{content}" + "#{indent}#{from} ->> #{to}: #{content}" when :return - "#{indent}#{from&.alias_name} -->> #{to&.alias_name}: #{content}" + "#{indent}#{from} -->> #{to}: #{content}" when :activate - "#{indent}activate #{to&.alias_name}" + "#{indent}activate #{to}" when :deactivate - "#{indent}deactivate #{to&.alias_name}" + "#{indent}deactivate #{to}" when :loop_start "#{indent}loop #{content}" when :loop_end @@ -44,6 +44,25 @@ def message(message) def indent " " * fetch_general_config(:tab_size) end + + def sanitize_name(name) + return "" if name.nil? + + # Convert Symbols to Strings and handle string sanitization + name = name.to_s if name.is_a?(Symbol) + + # Handle `#` specifically + name = name.gsub(/^#$/, "") + + # Replace unconventional method names with readable alternatives + name = name.gsub("[]=", "set_value") # Replace `[]=` with `set_value` + .gsub(/<<\z/, "append") # Replace `<<` with `append` + .gsub("[]", "get_value") # Replace `[]` with `get_value` + .gsub("...", "variadic") # Replace `...` with `variadic` + .gsub(/\A=/, "assign") # Replace `=` at the start with `assign` + + name + end end end end From 55f66a4b7df8297a588fc35b4bb4edc7a8f10696 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 13 Jan 2025 14:36:36 +0700 Subject: [PATCH 08/11] Refactor message extraction logic to improve parameter handling and enhance clarity in child node processing --- lib/trace_viz/extractors/diagram/message_extractor.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/trace_viz/extractors/diagram/message_extractor.rb b/lib/trace_viz/extractors/diagram/message_extractor.rb index f8da347..d23bd04 100644 --- a/lib/trace_viz/extractors/diagram/message_extractor.rb +++ b/lib/trace_viz/extractors/diagram/message_extractor.rb @@ -27,8 +27,10 @@ def extract attr_reader :message_builder, :participants_manager - def traverse(node, caller_trace) + def traverse(node, caller_node) trace = node.data + caller_trace = caller_node&.data + messages = [] # Handle inter-participant messages @@ -44,7 +46,7 @@ def traverse(node, caller_trace) messages << message_builder.build_activate_message(trace) if node_has_children?(trace) # Process child nodes - messages.concat(process_children(node, trace)) + messages.concat(process_children(node)) # Deactivation of participant messages << message_builder.build_deactivate_message(trace) if node_has_children?(trace) @@ -80,8 +82,8 @@ def handle_return_message(caller_trace, current_trace) end end - def process_children(node, trace) - node.children.flat_map { |child| traverse(child, trace) } + def process_children(node) + node.children.flat_map { |child| traverse(child, node) } end # -- Helper methods for logic -- From d965b26aafa7b1f07187e5b2ac7ff36ca4dc6a5d Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 13 Jan 2025 15:23:51 +0700 Subject: [PATCH 09/11] Refactor message building logic to eliminate redundant handling methods and improve clarity; add root? method to RootNode class and update key method in base class --- .../builders/diagram/message_builder.rb | 4 +++ .../extractors/diagram/message_extractor.rb | 28 ++----------------- lib/trace_viz/trace_data/base.rb | 2 +- lib/trace_viz/trace_data/root_node.rb | 4 +++ lib/trace_viz/utils/hierarchy_flattener.rb | 27 ------------------ 5 files changed, 12 insertions(+), 53 deletions(-) delete mode 100644 lib/trace_viz/utils/hierarchy_flattener.rb diff --git a/lib/trace_viz/builders/diagram/message_builder.rb b/lib/trace_viz/builders/diagram/message_builder.rb index 51584d8..b0fd40a 100644 --- a/lib/trace_viz/builders/diagram/message_builder.rb +++ b/lib/trace_viz/builders/diagram/message_builder.rb @@ -19,6 +19,8 @@ def build(type, from, to, content) end def build_call_message(from_trace, to_trace) + return if from_trace.klass == to_trace.klass + build( :call, participants_manager.find(from_trace.klass), @@ -28,6 +30,8 @@ def build_call_message(from_trace, to_trace) end def build_return_message(from_trace, to_trace) + return if from_trace.klass == to_trace.klass + build( :return, participants_manager.find(from_trace.klass), diff --git a/lib/trace_viz/extractors/diagram/message_extractor.rb b/lib/trace_viz/extractors/diagram/message_extractor.rb index d23bd04..60fd79d 100644 --- a/lib/trace_viz/extractors/diagram/message_extractor.rb +++ b/lib/trace_viz/extractors/diagram/message_extractor.rb @@ -34,13 +34,13 @@ def traverse(node, caller_node) messages = [] # Handle inter-participant messages - messages << handle_call_message(caller_trace, trace) + messages << message_builder.build_call_message(caller_trace, trace) if caller_trace # Handle loop structures messages << message_builder.build_loop_start_message(trace) if loop?(trace) # Internal message - messages << message_builder.build_internal_message(trace) + messages << message_builder.build_internal_message(trace) unless trace.root? # Activation of participant messages << message_builder.build_activate_message(trace) if node_has_children?(trace) @@ -55,33 +55,11 @@ def traverse(node, caller_node) messages << message_builder.build_loop_end_message if loop?(trace) # Handle return messages - messages << handle_return_message(caller_trace, trace) + messages << message_builder.build_return_message(trace, caller_trace) if caller_trace messages.compact end - def handle_call_message(caller_trace, current_trace) - return unless caller_trace - - from_participant = participants_manager.find(caller_trace.klass) - to_participant = participants_manager.find(current_trace.klass) - - if from_participant != to_participant - message_builder.build_call_message(caller_trace, current_trace) - end - end - - def handle_return_message(caller_trace, current_trace) - return unless caller_trace - - from_participant = participants_manager.find(caller_trace.klass) - to_participant = participants_manager.find(current_trace.klass) - - if from_participant != to_participant - message_builder.build_return_message(current_trace, caller_trace) - end - end - def process_children(node) node.children.flat_map { |child| traverse(child, node) } end diff --git a/lib/trace_viz/trace_data/base.rb b/lib/trace_viz/trace_data/base.rb index abd995c..975e729 100644 --- a/lib/trace_viz/trace_data/base.rb +++ b/lib/trace_viz/trace_data/base.rb @@ -19,7 +19,7 @@ def initialize # Represents trace data type code # def key - raise NotImplementedError + :base end def event diff --git a/lib/trace_viz/trace_data/root_node.rb b/lib/trace_viz/trace_data/root_node.rb index 192fef8..23d8552 100644 --- a/lib/trace_viz/trace_data/root_node.rb +++ b/lib/trace_viz/trace_data/root_node.rb @@ -12,6 +12,10 @@ def initialize @parent = nil end + def root? + true + end + def parent=(_parent) raise NoMethodError, "RootNode cannot have a parent" end diff --git a/lib/trace_viz/utils/hierarchy_flattener.rb b/lib/trace_viz/utils/hierarchy_flattener.rb deleted file mode 100644 index 3a5e64e..0000000 --- a/lib/trace_viz/utils/hierarchy_flattener.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module TraceViz - module Utils - class HierarchyFlattener - class << self - def flatten(root) - result = [] - traverse(root) do |node| - result << node - end - result - end - - private - - # Make sure already have .children method - def traverse(node, &block) - yield node - node.children.each do |child| - traverse(child, &block) - end - end - end - end - end -end From 8e975d7cecc55f3fb9b550342ed43f64d7708ed0 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 13 Jan 2025 15:34:20 +0700 Subject: [PATCH 10/11] Refactor message building and extraction logic to streamline method calls and enhance clarity; simplify message formatting in the sequence diagram --- .../builders/diagram/message_builder.rb | 4 +- .../extractors/diagram/message_extractor.rb | 73 +++++++++++++------ .../diagram/sequence/message_formatter.rb | 4 +- 3 files changed, 56 insertions(+), 25 deletions(-) diff --git a/lib/trace_viz/builders/diagram/message_builder.rb b/lib/trace_viz/builders/diagram/message_builder.rb index b0fd40a..4df1020 100644 --- a/lib/trace_viz/builders/diagram/message_builder.rb +++ b/lib/trace_viz/builders/diagram/message_builder.rb @@ -25,7 +25,7 @@ def build_call_message(from_trace, to_trace) :call, participants_manager.find(from_trace.klass), participants_manager.find(to_trace.klass), - formatter.format_call(to_trace), + formatter.format_call, ) end @@ -36,7 +36,7 @@ def build_return_message(from_trace, to_trace) :return, participants_manager.find(from_trace.klass), participants_manager.find(to_trace.klass), - formatter.format_return(from_trace, to_trace), + formatter.format_return, ) end diff --git a/lib/trace_viz/extractors/diagram/message_extractor.rb b/lib/trace_viz/extractors/diagram/message_extractor.rb index 60fd79d..9028b08 100644 --- a/lib/trace_viz/extractors/diagram/message_extractor.rb +++ b/lib/trace_viz/extractors/diagram/message_extractor.rb @@ -20,48 +20,79 @@ def initialize(collector, participants) def extract root = data - root.children.flat_map { |child| traverse(child, nil) } + root.children.flat_map { |child| process_node(child, nil) } end private attr_reader :message_builder, :participants_manager - def traverse(node, caller_node) + def process_node(node, caller_node) trace = node.data caller_trace = caller_node&.data - messages = [] + [].tap do |messages| + messages << handle_inter_participant_message(caller_trace, trace) + messages << handle_loop_start(trace) + messages << handle_internal_message(trace) + messages << handle_activation(trace) + messages.concat(process_children(node)) + messages << handle_deactivation(trace) + messages << handle_loop_end(trace) + messages << handle_return_message(trace, caller_trace) + end.compact + end + + # Handle inter-participant messages + def handle_inter_participant_message(caller_trace, trace) + return unless caller_trace - # Handle inter-participant messages - messages << message_builder.build_call_message(caller_trace, trace) if caller_trace + message_builder.build_call_message(caller_trace, trace) + end - # Handle loop structures - messages << message_builder.build_loop_start_message(trace) if loop?(trace) + # Handle loop start messages + def handle_loop_start(trace) + return unless loop?(trace) - # Internal message - messages << message_builder.build_internal_message(trace) unless trace.root? + message_builder.build_loop_start_message(trace) + end - # Activation of participant - messages << message_builder.build_activate_message(trace) if node_has_children?(trace) + # Handle internal messages + def handle_internal_message(trace) + message_builder.build_internal_message(trace) + end - # Process child nodes - messages.concat(process_children(node)) + # Handle activation of participants + def handle_activation(trace) + return unless node_has_children?(trace) - # Deactivation of participant - messages << message_builder.build_deactivate_message(trace) if node_has_children?(trace) + message_builder.build_activate_message(trace) + end - # End loop structures - messages << message_builder.build_loop_end_message if loop?(trace) + # Handle deactivation of participants + def handle_deactivation(trace) + return unless node_has_children?(trace) + + message_builder.build_deactivate_message(trace) + end + + # Handle loop end messages + def handle_loop_end(trace) + return unless loop?(trace) + + message_builder.build_loop_end_message + end - # Handle return messages - messages << message_builder.build_return_message(trace, caller_trace) if caller_trace + # Handle return messages + def handle_return_message(trace, caller_trace) + return unless caller_trace - messages.compact + message_builder.build_return_message(trace, caller_trace) end + # Process child nodes recursively def process_children(node) - node.children.flat_map { |child| traverse(child, node) } + node.children.flat_map { |child| process_node(child, node) } end # -- Helper methods for logic -- diff --git a/lib/trace_viz/formatters/diagram/sequence/message_formatter.rb b/lib/trace_viz/formatters/diagram/sequence/message_formatter.rb index 3860e4f..f0c8358 100644 --- a/lib/trace_viz/formatters/diagram/sequence/message_formatter.rb +++ b/lib/trace_viz/formatters/diagram/sequence/message_formatter.rb @@ -7,11 +7,11 @@ module Formatters module Diagram module Sequence class MessageFormatter < BaseFormatter - def format_call(to_trace) + def format_call "Calling" end - def format_return(from_trace, to_trace) + def format_return "Returning" end end From afecf09f3a5a91d11fa97c1f795c94ac7fa0940a Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 13 Jan 2025 16:05:05 +0700 Subject: [PATCH 11/11] Refactor message extraction logic by introducing SequenceNodeProcessor to streamline node processing and enhance clarity --- .../extractors/diagram/message_extractor.rb | 90 +------------------ .../processors/sequence_node_processor.rb | 88 ++++++++++++++++++ 2 files changed, 91 insertions(+), 87 deletions(-) create mode 100644 lib/trace_viz/extractors/diagram/processors/sequence_node_processor.rb diff --git a/lib/trace_viz/extractors/diagram/message_extractor.rb b/lib/trace_viz/extractors/diagram/message_extractor.rb index 9028b08..00e9b08 100644 --- a/lib/trace_viz/extractors/diagram/message_extractor.rb +++ b/lib/trace_viz/extractors/diagram/message_extractor.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true require "trace_viz/transformers/summary_transformer" -require "trace_viz/formatters/diagram/sequence/message_formatter" -require "trace_viz/builders/diagram/message_builder" -require "trace_viz/managers/diagram/participant_manager" +require "trace_viz/extractors/diagram/processors/sequence_node_processor" require_relative "../base_extractor" module TraceViz @@ -13,98 +11,16 @@ class MessageExtractor < BaseExtractor def initialize(collector, participants) super(collector) - @formatter = Formatters::Diagram::Sequence::MessageFormatter.new - @message_builder = Builders::Diagram::MessageBuilder.new(@formatter, participants) - @participants_manager = Managers::Diagram::ParticipantsManager.new(participants) + @node_processor = Processors::SequenceNodeProcessor.new(participants) end def extract root = data - root.children.flat_map { |child| process_node(child, nil) } + root.children.flat_map { |child| @node_processor.process_node(child) } end private - attr_reader :message_builder, :participants_manager - - def process_node(node, caller_node) - trace = node.data - caller_trace = caller_node&.data - - [].tap do |messages| - messages << handle_inter_participant_message(caller_trace, trace) - messages << handle_loop_start(trace) - messages << handle_internal_message(trace) - messages << handle_activation(trace) - messages.concat(process_children(node)) - messages << handle_deactivation(trace) - messages << handle_loop_end(trace) - messages << handle_return_message(trace, caller_trace) - end.compact - end - - # Handle inter-participant messages - def handle_inter_participant_message(caller_trace, trace) - return unless caller_trace - - message_builder.build_call_message(caller_trace, trace) - end - - # Handle loop start messages - def handle_loop_start(trace) - return unless loop?(trace) - - message_builder.build_loop_start_message(trace) - end - - # Handle internal messages - def handle_internal_message(trace) - message_builder.build_internal_message(trace) - end - - # Handle activation of participants - def handle_activation(trace) - return unless node_has_children?(trace) - - message_builder.build_activate_message(trace) - end - - # Handle deactivation of participants - def handle_deactivation(trace) - return unless node_has_children?(trace) - - message_builder.build_deactivate_message(trace) - end - - # Handle loop end messages - def handle_loop_end(trace) - return unless loop?(trace) - - message_builder.build_loop_end_message - end - - # Handle return messages - def handle_return_message(trace, caller_trace) - return unless caller_trace - - message_builder.build_return_message(trace, caller_trace) - end - - # Process child nodes recursively - def process_children(node) - node.children.flat_map { |child| process_node(child, node) } - end - - # -- Helper methods for logic -- - - def loop?(trace) - trace.key == :summary_group - end - - def node_has_children?(trace) - trace.children.any? - end - def data @data ||= Transformers::SummaryTransformer.new(collector).transform end diff --git a/lib/trace_viz/extractors/diagram/processors/sequence_node_processor.rb b/lib/trace_viz/extractors/diagram/processors/sequence_node_processor.rb new file mode 100644 index 0000000..3b98c0c --- /dev/null +++ b/lib/trace_viz/extractors/diagram/processors/sequence_node_processor.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require "trace_viz/formatters/diagram/sequence/message_formatter" +require "trace_viz/builders/diagram/message_builder" + +module TraceViz + module Extractors + module Diagram + module Processors + class SequenceNodeProcessor + def initialize(participants) + @formatter = Formatters::Diagram::Sequence::MessageFormatter.new + @message_builder = Builders::Diagram::MessageBuilder.new(@formatter, participants) + end + + def process_node(node, caller_node = nil) + trace = node.data + caller_trace = caller_node&.data + + [].tap do |messages| + # Handle participant transitions + messages << @message_builder.build_call_message( + caller_trace, + trace, + ) if caller_trace + + # Process the current node + messages << handle_loop_start(trace) + messages << handle_internal_message(trace) + messages << handle_activation(trace) + messages.concat(process_children(node)) + messages << handle_deactivation(trace) + messages << handle_loop_end(trace) + + # Update the current node after processing + messages << @message_builder.build_return_message( + trace, + caller_trace, + ) if caller_trace + end.compact + end + + private + + def handle_loop_start(trace) + return unless loop?(trace) + + @message_builder.build_loop_start_message(trace) + end + + def handle_internal_message(trace) + @message_builder.build_internal_message(trace) + end + + def handle_activation(trace) + return unless node_has_children?(trace) + + @message_builder.build_activate_message(trace) + end + + def handle_deactivation(trace) + return unless node_has_children?(trace) + + @message_builder.build_deactivate_message(trace) + end + + def handle_loop_end(trace) + return unless loop?(trace) + + @message_builder.build_loop_end_message + end + + def process_children(node) + node.children.flat_map { |child| process_node(child, node) } + end + + def loop?(trace) + trace.key == :summary_group + end + + def node_has_children?(trace) + trace.children.any? + end + end + end + end + end +end