/******************************************************************************* * Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * *******************************************************************************/ package org.eclipse.xtend.profiler; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.xtend.profiler.profilermodel.CallGroup; import org.eclipse.xtend.profiler.profilermodel.Cycle; import org.eclipse.xtend.profiler.profilermodel.Item; import org.eclipse.xtend.profiler.profilermodel.ModelFactory; import org.eclipse.xtend.profiler.profilermodel.ProfilingResult; /** * Detects cycles in call graph of a given profiling result. This implementation uses * a slightly modified Tarjan's strongly connected components (SCC) algorithm to accept * trivial SCCs only if there is an reflexive edge (self recursion). * * @see http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm * @author Heiko Behrens - Initial contribution and API */ public class CycleDetector { private final ProfilingResult profilingResult; public CycleDetector(ProfilingResult result) { profilingResult = result; } private void tarjan(Item item) { visited.add(item); setLowLink(item, getIndex(item)); stack.add(0, item); for (CallGroup group : item.getSubroutines()) { Item sub = group.getSubroutine(); if (!hasBeenVisited(sub)) { tarjan(sub); setLowLinkIfSmaller(item, getLowLink(sub)); } else if(stack.contains(sub)) setLowLinkIfSmaller(item, getIndex(sub)); } if(getLowLink(item) == getIndex(item)) buildCycleFromStack(item); } private void buildCycleFromStack(Item item) { List<Item> items = new ArrayList<Item>(); while (true) { Item v = stack.remove(0); // use inverted order to support human's understanding items.add(0, v); if (item.equals(v)) break; } if (items.size() > 1 || isSelfRecursion(item)) { Cycle cycle = ModelFactory.eINSTANCE.createCycle(); cycle.getItems().addAll(items); // use inverted order to support human's understanding profilingResult.getCycles().add(0, cycle); } } private boolean isSelfRecursion(Item item) { for (CallGroup g : item.getSubroutines()) if (item.equals(g.getSubroutine())) return true; return false; } List<Item> visited = new ArrayList<Item>(); List<Item> stack = new ArrayList<Item>(); Map<Item, Integer> lowLink = new HashMap<Item, Integer>(); public void detectCycles() { profilingResult.getCycles().clear(); Set<Item> toBeVisited = new LinkedHashSet<Item>(profilingResult.getItems()); while(!toBeVisited.isEmpty()) { Item item = toBeVisited.iterator().next(); tarjan(item); toBeVisited.removeAll(visited); } } private void setLowLink(Item item, int value) { lowLink.put(item, value); } private void setLowLinkIfSmaller(Item item, int potentiallySmallerValue) { setLowLink(item, Math.min(getLowLink(item), potentiallySmallerValue)); } private int getLowLink(Item item) { return lowLink.get(item); } private boolean hasBeenVisited(Item item) { return visited.contains(item); } private int getIndex(Item item) { if(!hasBeenVisited(item)) throw new IllegalStateException("Item has not been visited, yet."); return visited.indexOf(item); } }