package com.insightfullogic.honest_profiler.ports.javafx.model.task;
import static com.insightfullogic.honest_profiler.core.Monitor.pipe;
import static com.insightfullogic.honest_profiler.core.Monitor.pipeFile;
import static com.insightfullogic.honest_profiler.ports.javafx.model.ProfileContext.ProfileMode.LIVE;
import static com.insightfullogic.honest_profiler.ports.javafx.model.ProfileContext.ProfileMode.LOG;
import java.io.File;
import com.insightfullogic.honest_profiler.core.collector.FlameGraphCollector;
import com.insightfullogic.honest_profiler.core.collector.lean.LeanLogCollector;
import com.insightfullogic.honest_profiler.core.parser.LogEventListener;
import com.insightfullogic.honest_profiler.core.parser.LogEventPublisher;
import com.insightfullogic.honest_profiler.core.profiles.lean.LeanProfile;
import com.insightfullogic.honest_profiler.core.sources.VirtualMachine;
import com.insightfullogic.honest_profiler.ports.javafx.model.ApplicationContext;
import com.insightfullogic.honest_profiler.ports.javafx.model.ProfileContext;
import com.insightfullogic.honest_profiler.ports.javafx.model.ProfileContext.ProfileMode;
import com.insightfullogic.honest_profiler.ports.sources.FileLogSource;
import javafx.concurrent.Task;
/**
* Background task which opens the log file produced by a Profiler Agent, chains together the file, file parser and
* {@link LeanLogCollector}, and creates a {@link ProfileContext} for the profile.
*/
public class InitializeProfileTask extends Task<ProfileContext>
{
// Instance Properties
private final ApplicationContext appCtx;
private final Object source;
private final boolean live;
// Instance Constructors
/**
* Constructor which specifies the {@link ApplicationContext}, the source of the profile (either a live VM or a log
* file) and an indication whether the monitoring is "live" or not (i.e. whether the Profiler Agent generating the
* source is still running and appending data).
* <p>
* @param applicationContext the {@link ApplicationContext} for the application
* @param source the source {@link VirtualMachine} or {@link FileLogSource}
* @param live a boolean indicating whether the log file is "live"
*/
public InitializeProfileTask(ApplicationContext applicationContext, Object source, boolean live)
{
super();
appCtx = applicationContext;
this.source = source;
this.live = live;
}
@Override
protected ProfileContext call() throws Exception
{
FileLogSource fileLogSource = getLogSource();
return (source instanceof VirtualMachine || live) ? monitor(fileLogSource)
: consume(fileLogSource);
}
// Guaranteed to be called on the FX thread.
@Override
protected void succeeded()
{
super.succeeded();
// We do this on the FX thread to avoid concurrency issues with the ProfileContext map read/write access in the
// ApplicationContext.
appCtx.registerProfileContext(this.getValue());
}
// Guaranteed to be called on the FX thread.
@Override
protected void failed()
{
super.failed();
// This should be shown in an error dialog someday instead.
getException().printStackTrace();
}
/**
* Create a new {@link ProfileContext} instance.
* <p>
* @param mode the {@link ProfileContext.ProfileMode} for the {@link ProfileContext}
* @param fileLogSource the {@link FileLogSource} exposing the log file from the Profiler Agent
* @return a new {@link ProfileContext}
*/
private ProfileContext newProfileContext(ProfileMode mode, FileLogSource fileLogSource)
{
return new ProfileContext(appCtx, getName(), mode, fileLogSource.getFile());
}
/**
* Returns a {@link LeanLogCollector} which emits {@link LeanProfile}s to the specified {@link ProfileContext}.
* <p>
* @param context the {@link ProfileContext} which will receive the emitted {@link LeanProfile}s
* @return a new {@link LeanLogCollector}
*/
private LeanLogCollector getCollector(ProfileContext context)
{
return new LeanLogCollector(context.getProfileListener());
}
/**
* Returns either the original source specified on {@link Task} creation if it is already a {@link FileLogSource},
* or it gets the {@link FileLogSource} from the source {@link VirtualMachine}.
* <p>
* @return the {@link FileLogSource} for the profile specified by the task source
*/
private FileLogSource getLogSource()
{
return (source instanceof VirtualMachine)
? (FileLogSource)((VirtualMachine)source).getLogSourceFromVmArgs()
: new FileLogSource((File)source);
}
/**
* Return a name constructed from information from the task source Object.
* <p>
* @return a name based on the task source Object
*/
private String getName()
{
return (source instanceof VirtualMachine) ? getVmName((VirtualMachine)source)
: ((File)source).getName();
}
/**
* Returns a {@link ProfileContext} which monitors {@link LeanProfile}s emitted by a {@link LeanLogCollector} based
* on a live log file.
* <p>
* @param fileLogSource the live log file from which the log events for constructing the {@link LeanProfile} are
* sourced
* @return a new {@link ProfileContext} for live monitoring
*/
private ProfileContext monitor(FileLogSource fileLogSource)
{
ProfileContext profileContext = newProfileContext(LIVE, fileLogSource);
LeanLogCollector collector = getCollector(profileContext);
profileContext.setProfileSource(collector);
pipeFile(fileLogSource, collector, profileContext.getProfileListener());
return profileContext;
}
/**
* Returns a {@link ProfileContext} which will emit {@link LeanProfile}s produced by consuming a non-live log file.
* <p>
* @param fileLogSource the non-live log file which will be processed
* @return a new {@link ProfileContext} for non-live log file comsumption
*/
private ProfileContext consume(FileLogSource fileLogSource)
{
ProfileContext profileContext = newProfileContext(LOG, fileLogSource);
final LogEventListener collector = new LogEventPublisher()
// Multiplex Log Events to the LeanLogCollector
.publishTo(getCollector(profileContext))
// Multiplex Log Events to the FlameGraphCollector
.publishTo(new FlameGraphCollector(profileContext.getFlameGraphListener()));
pipe(fileLogSource, collector, false).run();
return profileContext;
}
/**
* Creates a name for a {@link VirtualMachine} which will be monitored.
* <p>
* @param vm the {@link VirtualMachine} which will be monitored
* @return a name for the {@link VirtualMachine}
*/
private String getVmName(VirtualMachine vm)
{
String name = vm.getDisplayName();
StringBuilder result = new StringBuilder();
result.append((name.contains(" ")) ? name.substring(0, name.indexOf(" ")) : name);
result.append(" (").append(vm.getId()).append(")");
return result.toString();
}
}