Skip to content

Allow for timeline event traversal honouring the parent - child relationship #27475

Open
@marcingrzejszczak

Description

@marcingrzejszczak

The BufferingApplicationStartup returns a StartupTimeline that contains events. Those events have a parent - child relationship (a TimelineEvent has access to BufferedStartupStep that can have a parent id).

The problem is that we can't traverse this tree of events in a proper order. We need to build the graph ourselves.

@jonatan-ivanov has prototyped such traversal in the following manner:

        @ReadOperation
	public void observabilitySnapshot() {
		StartupTimeline startupTimeline = this.applicationStartup.getBufferedTimeline();
		observeStartupTimeline(startupTimeline);
	}

	private void observeStartupTimeline(StartupTimeline startupTimeline) {
		if (startupTimeline.getEvents().isEmpty()) {
			return;
		}

		// We're building a map of IDs to nodes
		Map<Long, Node> stepMap = startupTimeline.getEvents().stream()
				.map(Node::new)
				.collect(toMap(node -> node.startupStep.getId(), Function.identity()));


		// Some startupsteps do not have a parent so we need a root one. It's start will be the timeline's start time and end will be the last event's end time
		Node artificalRoot = new Node(new StartupStep() {
			@Override
			public String getName() {
				return "a name";
			}

			@Override
			public long getId() {
				return -100;
			}

			@Override
			public Long getParentId() {
				return null;
			}

			@Override
			public StartupStep tag(String key, String value) {
				return null;
			}

			@Override
			public StartupStep tag(String key, Supplier<String> value) {
				return null;
			}

			@Override
			public Tags getTags() {
				return null;
			}

			@Override
			public void end() {

			}
		}, toNanos(startupTimeline.getStartTime()), stepMap.entrySet().stream().max(Map.Entry.comparingByKey()).get().getValue().endTimeNanos);

		// we're adding for each node its corresponding children
		for (Map.Entry<Long, Node> entry : stepMap.entrySet()) {
			Node current = entry.getValue();
			Node parent = stepMap.get(current.startupStep.getParentId() != null ? current.startupStep.getParentId() : artificalRoot);
			parent = parent != null ? parent : artificalRoot;
			parent.children.add(current);
		}
		visit(artificalRoot);
	}


	// Recursive node visiting
	private void visit(Node node) {
              // e.g. generate a span
              T t = doSomeWorkBefore(node);

		// TODO: add filtering over duration otherwise the graph can blow up (e.g. maybe merge manually various children)
		for (Node child : node.children) {
			visit(child);
		}
              // e.g. close a span
               doSomeWorkAfter(node, t);
	}


	static class Node {
		private final StartupStep startupStep;
		private final long startTimeNanos;
		private final long endTimeNanos;
		List<Node> children = new ArrayList<>();

		Node(StartupTimeline.TimelineEvent timelineEvent) {
			this.startupStep = timelineEvent.getStartupStep();
			this.startTimeNanos = toNanos(timelineEvent.getStartTime());
			this.endTimeNanos = toNanos(timelineEvent.getEndTime());
		}

		Node(StartupStep startupStep, long startTimeNanos, long endTimeNanos) {
			this.startupStep = startupStep;
			this.startTimeNanos = startTimeNanos;
			this.endTimeNanos = endTimeNanos;
		}

		private long toNanos(Instant time) {
			return TimeUnit.SECONDS.toNanos(time.getEpochSecond()) + time.getNano();
		}
	}

It would be great if such node traversal was done from within Boot, e.g. the StartupTimeline would have a method like traverse that would give us Function<Node, T> beforeVisit and BiConsumer<Node, T> afterVisit.

cc @jonatan-ivanov @shakuzen @bclozel

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions