All Downloads are FREE. Search and download functionalities are using the official Maven repository.

no.sysco.middleware.kafka.util.StreamsTopologyGraphviz Maven / Gradle / Ivy

package no.sysco.middleware.kafka.util;

import io.github.livingdocumentation.dotdiagram.DotGraph;
import java.util.stream.Stream;
import org.apache.kafka.streams.Topology;
import org.apache.kafka.streams.TopologyDescription;

/**
 * Utility for printing Kafka Streams Topologies in Graphviz format.
 */
public class StreamsTopologyGraphviz {

  /**
   * Creates a Graphviz from Kafka Streams {@link Topology}
   *
   * @return Graphviz text
   */
  public static String print(Topology topology) {
    final TopologyDescription topologyDescription = topology.describe();
    final DotGraph dotGraph = new DotGraph("kafka-streams topology");
    final DotGraph.Digraph digraph = dotGraph.getDigraph();

    printTopics(topologyDescription, digraph);

    printStores(topologyDescription, digraph);

    printSubtopologies(topologyDescription, digraph);

    return dotGraph.render();
  }

  private static void printSubtopologies(TopologyDescription topologyDescription,
      DotGraph.Digraph digraph) {
    topologyDescription
        .subtopologies()
        .forEach(subtopology -> printSubtopology(subtopology, digraph));
  }

  private static void printStores(TopologyDescription topologyDescription,
      DotGraph.Digraph digraph) {
    topologyDescription.subtopologies().stream()
        .flatMap(subtopology -> subtopology.nodes().stream())
        .filter(node -> node instanceof TopologyDescription.Processor)
        .flatMap(node -> ((TopologyDescription.Processor) node).stores().stream())
        .distinct()
        .forEach(topic ->
            digraph
                .addNode(String.format("store-%s", topic))
                .setLabel(String.format("Store: %s", topic))
                .setOptions("shape=box3d"));
  }

  private static void printTopics(TopologyDescription topologyDescription,
      DotGraph.Digraph digraph) {
    final Stream sourceTopics =
        topologyDescription.subtopologies().stream()
            .flatMap(subtopology -> subtopology.nodes().stream())
            .filter(node -> node instanceof TopologyDescription.Source)
            .flatMap(node -> {
              final String topics = ((TopologyDescription.Source) node).topics();
              return Stream.of(topics.substring(1, topics.length() - 1).split(","));
            })
            .map(String::trim);

    final Stream sinkTopics =
        topologyDescription.subtopologies().stream()
            .flatMap(subtopology -> subtopology.nodes().stream())
            .filter(node -> node instanceof TopologyDescription.Sink)
            .map(node -> ((TopologyDescription.Sink) node).topic());

    Stream.concat(sourceTopics, sinkTopics)
        .distinct()
        .forEach(topic ->
            digraph
                .addNode(String.format("topic-%s", topic))
                .setOptions("shape=cds")
                .setLabel(String.format("Topic: %s", topic)));
  }

  private static void printSubtopology(TopologyDescription.Subtopology subtopology,
      DotGraph.Digraph digraph) {
    DotGraph.Cluster cluster =
        (DotGraph.Cluster) digraph.addCluster(String.format("topology_%s", subtopology.id()))
            .setLabel(String.format("Sub-Topology: %s", subtopology.id()));

    for (TopologyDescription.Node node : subtopology.nodes()) {

      if (node instanceof TopologyDescription.Source) {
        String processorId = String.format("node-%s", node.name());
        cluster.addNode(processorId)
            .setLabel(String.format("Source: %s", node.name()));

        final String topics = ((TopologyDescription.Source) node).topics();

        Stream.of(topics.substring(1, topics.length() - 1).split(","))
            .map(String::trim)
            .forEach(topic -> {
              String topicId = String.format("topic-%s", topic);
              cluster.addNode(topicId)
                  .setLabel(String.format("Topic: %s", topic))
                  .addAssociation(topicId, processorId);
            });
      } else if (node instanceof TopologyDescription.Processor) {
        String processorId = String.format("node-%s", node.name());
        DotGraph.AbstractNode processorNode = cluster.addNode(processorId)
            .setLabel(String.format("Source: %s", node.name()));

        for (TopologyDescription.Node predecessor : node.predecessors()) {
          String predecessorId = String.format("node-%s", predecessor.name());
          processorNode.addAssociation(predecessorId, processorId);
        }

        for (String store : ((TopologyDescription.Processor) node).stores()) {
          String storeId = String.format("store-%s", store);
          processorNode.addAssociation(processorId, storeId);
        }
      } else if (node instanceof TopologyDescription.Sink) {
        String processorId = String.format("node-%s", node.name());
        DotGraph.AbstractNode sinkNode =
            cluster.addNode(processorId).setLabel(String.format("Sink: %s", node.name()));

        for (TopologyDescription.Node predecessor : node.predecessors()) {
          String predecessorId = String.format("node-%s", predecessor.name());
          sinkNode.addAssociation(predecessorId, processorId);
        }

        String topic = ((TopologyDescription.Sink) node).topic();
        String topicId = String.format("topic-%s", topic);
        sinkNode.addAssociation(processorId, topicId);
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy