/******************************************************************************* * 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.List; import java.util.Map; import org.eclipse.xtend.profiler.profilermodel.CallGroup; import org.eclipse.xtend.profiler.profilermodel.Item; import org.eclipse.xtend.profiler.profilermodel.ModelFactory; import org.eclipse.xtend.profiler.profilermodel.ProfilingResult; /** * Records method calls and the resulting call stack to build up a profiling result. * Please understand that the given implementation is not aware of threads but uses * a simple idea of a linearity. Also, the implementation uses a TimeProvider that * per default relies on the system time. This might lead into problems when hibernating * the execution environment or debugging the runtime system. * * The output is will not be scanned for cycles. Use a CycleDetector as a post-processing * step for this * * @see org.eclipse.xtend.profiler.CycleDetector * @author Heiko Behrens - Initial contribution and API * * TODO: Make this class acting as a facade that can handle cycles by itself and offers * result only after the internal state has been fixed to prevent further modifications. * */ public class Profiler { public interface TimeProvider { long getNanoSeconds(); } private TimeProvider timeProvider = new TimeProvider() { public long getNanoSeconds() { return System.nanoTime(); } }; public TimeProvider getTimeProvider() { return timeProvider; } public void setTimeProvider(TimeProvider timeProvider) { this.timeProvider = timeProvider; } class Call { final Item item; CallGroup callGroup; long startedAt; Call(Item item) { this.item = item; } void startCounting(CallGroup callGroup) { this.startedAt = timeProvider.getNanoSeconds(); this.callGroup = callGroup; } void freeze(boolean measureTimeForItem) { long neededTime = timeProvider.getNanoSeconds() - startedAt; if(item != null && measureTimeForItem) item.setItemTime(item.getItemTime() +neededTime); if(callGroup != null) callGroup.setTime(callGroup.getTime() +neededTime); } public boolean isSameName(String name) { return item != null && name.equals(item.getName()); } } private final ProfilingResult profilingResult; public ProfilingResult getProfilingResult() { return profilingResult; } List<Call> stack = new ArrayList<Call>(); Map<String, Item> items = new HashMap<String, Item>(); public Profiler() { this(ModelFactory.eINSTANCE.createProfilingResult()); } public Profiler(ProfilingResult profilingResult) { this.profilingResult = profilingResult; } private Item createItem(String key) { Item result = ModelFactory.eINSTANCE.createItem(); result.setItemName(key); items.put(key, result); profilingResult.getItems().add(result); return result; } public Item getItem(String key) { Item result = items.get(key); if (result == null) result = createItem(key); return result; } public void beginRoutine(String key) { if(key==null) beginRoutine(); else beginRoutine(getItem(key)); } private CallGroup getCallGroup(Item caller, Item subroutine) { for (CallGroup calls : caller.getSubroutines()) { if(calls.getSubroutine().equals(subroutine)) return calls; } CallGroup result = ModelFactory.eINSTANCE.createCallGroup(); caller.getSubroutines().add(result); result.setSubroutine(subroutine); return result; } private void beginRoutine(Item routine) { Call call = new Call(routine); CallGroup group = findCallGroup(routine); stack.add(0, call); call.startCounting(group); } private CallGroup findCallGroup(Item routine) { Item invoker = findLastInvoker(); CallGroup group = null; if(invoker != null) { group = getCallGroup(invoker, routine); group.setCount(group.getCount() + 1); } return group; } private void beginRoutine() { stack.add(0, new Call(null)); } private Item findLastInvoker() { for (Call call : stack) { if(call.item != null) return call.item; } return null; } public void endRoutine() { Call lastCall = stack.remove(0); lastCall.freeze(true); } public boolean hasFinished() { return stack.isEmpty(); } }