/*******************************************************************************
* Copyright (c) 2015 IBH SYSTEMS GmbH.
* 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
*
* Contributors:
* IBH SYSTEMS GmbH - initial API and implementation
*******************************************************************************/
package org.eclipse.packagedrone.utils.profiler;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.eclipse.packagedrone.utils.ProcessUtil;
import org.eclipse.packagedrone.utils.profiler.Profile.DurationEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class XmlProfileDataHandler implements ProfileDataHandler
{
private final static Logger logger = LoggerFactory.getLogger ( XmlProfileDataHandler.class );
/**
* 40 spaces, so that the indent method can use it for processing
*/
private static char[] whitespaces = " ".toCharArray ();
private final Path dir;
private final AtomicInteger counter = new AtomicInteger ();
private final Long pid;
public XmlProfileDataHandler ( final Path dir )
{
this.dir = dir;
this.pid = ProcessUtil.getProcessId ();
if ( this.pid == null )
{
throw new IllegalStateException ( String.format ( "Unable to evaluate current process ID" ) );
}
logger.warn ( "Activating XML profile data handler: {}/{}", dir, this.pid );
}
private Path makeNextName () throws IOException
{
if ( !Files.exists ( this.dir ) )
{
if ( Files.exists ( this.dir.getParent () ) )
{
// if the parent exists, create the actual directory, but only then
Files.createDirectory ( this.dir );
}
}
return this.dir.resolve ( String.format ( "pid%d-%08d.xml", this.pid, this.counter.getAndIncrement () ) );
}
@Override
public void handle ( final DurationEntry entry )
{
try
{
final XMLOutputFactory xml = XMLOutputFactory.newInstance ();
final Path name = makeNextName ();
try ( OutputStream stream = new BufferedOutputStream ( Files.newOutputStream ( name, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE ) ) )
{
final XMLStreamWriter xsw = xml.createXMLStreamWriter ( stream );
xsw.writeStartDocument ();
xsw.writeCharacters ( "\n\n" );
xsw.writeStartElement ( "trace" );
xsw.writeDefaultNamespace ( "http://packagedrone.org/profile/trace/v1.0" );
xsw.writeCharacters ( "\n" );
dumpEntry ( xsw, entry, 1 );
xsw.writeEndElement ();
xsw.writeCharacters ( "\n" ); // end white new line
xsw.writeEndDocument ();
}
if ( !Boolean.getBoolean ( "drone.profile.xml.disableAnnounce" ) )
{
System.out.format ( "Wrote profile trace: %s ms - %s -> %s%n", entry.getDuration ().toMillis (), entry.getOperation (), name.toAbsolutePath () );
}
}
catch ( final Exception e )
{
logger.warn ( "Failed to write profile trace", e );
}
}
private void dumpEntry ( final XMLStreamWriter xsw, final DurationEntry entry, final int level ) throws XMLStreamException
{
indent ( xsw, level );
final boolean empty = entry.getEntries ().isEmpty ();
if ( empty )
{
xsw.writeEmptyElement ( "operation" );
}
else
{
xsw.writeStartElement ( "operation" );
}
// write attributes
xsw.writeAttribute ( "name", entry.getOperation () );
xsw.writeAttribute ( "duration", "" + entry.getDuration ().toMillis () );
if ( !empty )
{
xsw.writeCharacters ( "\n" );
dumpEntries ( xsw, entry.getEntries (), level + 1 );
// end element
indent ( xsw, level );
xsw.writeEndElement ();
}
xsw.writeCharacters ( "\n" );
}
private void indent ( final XMLStreamWriter xsw, final int level ) throws XMLStreamException
{
xsw.writeCharacters ( whitespaces, 0, Math.min ( whitespaces.length, level * 2 ) );
}
private void dumpEntries ( final XMLStreamWriter xsw, final List<DurationEntry> entries, final int level ) throws XMLStreamException
{
for ( final DurationEntry entry : entries )
{
dumpEntry ( xsw, entry, level );
}
}
}