001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.client.trace; 019 020import io.opentelemetry.api.trace.SpanId; 021import io.opentelemetry.sdk.trace.data.SpanData; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.HashMap; 025import java.util.LinkedHashMap; 026import java.util.List; 027import java.util.ListIterator; 028import java.util.Map; 029import java.util.Objects; 030import java.util.function.Consumer; 031import java.util.stream.Collectors; 032import org.apache.commons.lang3.builder.ToStringBuilder; 033import org.apache.commons.lang3.builder.ToStringStyle; 034import org.apache.yetus.audience.InterfaceAudience; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038/** 039 * A Rudimentary tool for visualizing a hierarchy of spans. Given a collection of spans, indexes 040 * them from parents to children and prints them out one per line, indented. 041 */ 042@InterfaceAudience.Private 043public class StringTraceRenderer { 044 private static final Logger logger = LoggerFactory.getLogger(StringTraceRenderer.class); 045 046 private final List<Node> graphs; 047 048 public StringTraceRenderer(final Collection<SpanData> spans) { 049 final Map<String, Node> spansById = indexSpansById(spans); 050 populateChildren(spansById); 051 graphs = findRoots(spansById); 052 } 053 054 private static Map<String, Node> indexSpansById(final Collection<SpanData> spans) { 055 final Map<String, Node> spansById = new HashMap<>(spans.size()); 056 spans.forEach(span -> spansById.put(span.getSpanId(), new Node(span))); 057 return spansById; 058 } 059 060 private static void populateChildren(final Map<String, Node> spansById) { 061 spansById.forEach((spanId, node) -> { 062 final SpanData spanData = node.spanData; 063 final String parentSpanId = spanData.getParentSpanId(); 064 if (Objects.equals(parentSpanId, SpanId.getInvalid())) { 065 return; 066 } 067 final Node parentNode = spansById.get(parentSpanId); 068 if (parentNode == null) { 069 logger.warn("Span {} has parent {} that is not found in index, {}", spanId, parentSpanId, 070 spanData); 071 return; 072 } 073 parentNode.children.put(spanId, node); 074 }); 075 } 076 077 private static List<Node> findRoots(final Map<String, Node> spansById) { 078 return spansById.values().stream() 079 .filter(node -> Objects.equals(node.spanData.getParentSpanId(), SpanId.getInvalid())) 080 .collect(Collectors.toList()); 081 } 082 083 public void render(final Consumer<String> writer) { 084 for (ListIterator<Node> iter = graphs.listIterator(); iter.hasNext();) { 085 final int idx = iter.nextIndex(); 086 final Node node = iter.next(); 087 render(writer, node, 0, idx == 0); 088 } 089 } 090 091 private static void render(final Consumer<String> writer, final Node node, final int indent, 092 final boolean isFirst) { 093 writer.accept(render(node.spanData, indent, isFirst)); 094 final List<Node> children = new ArrayList<>(node.children.values()); 095 for (ListIterator<Node> iter = children.listIterator(); iter.hasNext();) { 096 final int idx = iter.nextIndex(); 097 final Node child = iter.next(); 098 render(writer, child, indent + 2, idx == 0); 099 } 100 } 101 102 private static String render(final SpanData spanData, final int indent, final boolean isFirst) { 103 final StringBuilder sb = new StringBuilder(); 104 for (int i = 0; i < indent; i++) { 105 sb.append(' '); 106 } 107 108 return sb.append(isFirst ? "└─ " : "├─ ").append(render(spanData)).toString(); 109 } 110 111 private static String render(final SpanData spanData) { 112 return new ToStringBuilder(spanData, ToStringStyle.NO_CLASS_NAME_STYLE) 113 .append("spanId", spanData.getSpanId()).append("name", spanData.getName()) 114 .append("hasEnded", spanData.hasEnded()).toString(); 115 } 116 117 private static class Node { 118 final SpanData spanData; 119 final LinkedHashMap<String, Node> children; 120 121 Node(final SpanData spanData) { 122 this.spanData = spanData; 123 this.children = new LinkedHashMap<>(); 124 } 125 } 126}