/*
 * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.glassfish.pfl.tf.tools.enhancer ;

import org.glassfish.pfl.basic.tools.argparser.DefaultValue ;
import org.glassfish.pfl.basic.tools.argparser.Help ;
import org.glassfish.pfl.basic.tools.argparser.ArgParser ;
import org.glassfish.pfl.basic.tools.file.ActionFactory;
import org.glassfish.pfl.basic.tools.file.Scanner ;
import org.glassfish.pfl.basic.tools.file.Recognizer ;
import org.glassfish.pfl.basic.tools.file.FileWrapper ;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Set;
import org.glassfish.pfl.tf.timer.spi.TimerPointSourceGenerator;
import org.glassfish.pfl.tf.timer.spi.TimingInfoProcessor;
import org.glassfish.pfl.tf.timer.spi.TimerFactory;
import org.glassfish.pfl.basic.contain.Pair;
import org.glassfish.pfl.basic.func.UnaryFunction;
import org.glassfish.pfl.tf.spi.Util;

Tool for enhancing classes annotated with tracing facility annotations. The processing is divided into two phases:
  1. The first phase adds code to the static initialized of the class to register the class with the TF framework. This phase also modifies all calls to @InfoMethod annotated methods to pass extra arguments needed for tracing. Note that this phase must be done at build time, because it modifies the schema of the class.
  2. The second phase adds the actual trace code to methods annotated with tracing annotations. This does not change the schema, so this phase could be done either at build time, or at runtime. Tracing the code only at runtime may reduce the overhead, since untraced code need not be modified.
This tool can do either phase 1, or phase 1 and 2, against ALL classes reachable from a starting directory.
Author:ken
/** Tool for enhancing classes annotated with tracing facility annotations. * The processing is divided into two phases: * <ol> * <li>The first phase adds code to the static initialized of the class to * register the class with the TF framework. This phase also modifies all * calls to @InfoMethod annotated methods to pass extra arguments needed for * tracing. Note that this phase <b>must</b> be done at build time, because it * modifies the schema of the class. * <li>The second phase adds the actual trace code to methods annotated with * tracing annotations. This does not change the schema, so this phase * could be done either at build time, or at runtime. Tracing the code only * at runtime may reduce the overhead, since untraced code need not be modified. * </ol> * This tool can do either phase 1, or phase 1 and 2, against ALL classes * reachable from a starting directory. * @author ken */
public class EnhanceTool { private static int errorCount = 0 ; private Util util ; public enum ProcessingMode {
Only generate the timing points file: no changes to monitored class bytecode.
/** Only generate the timing points file: no changes to monitored class * bytecode. */
TimingPoints,
Update the class schemas for monitored classes, but don't insert the tracing code. Generate the TimingPoints file if the timingPointDir and timingPointClass options are set.
/** Update the class schemas for monitored classes, but don't insert * the tracing code. Generate the TimingPoints file if the * timingPointDir and timingPointClass options are set. */
UpdateSchemas,
Update the class schema and insert tracing code. Generate the TimingPoints file if the timingPointDir and timingPointClass options are set.
/** Update the class schema and insert tracing code. Generate the * TimingPoints file if the timingPointDir and timingPointClass * options are set. */
TraceEnhance } public interface Arguments { @DefaultValue( "tfannotations.properties" ) @Help( "Name of resource file containing information " + "about tf annotations") File rf() ; @DefaultValue( "false" ) @Help( "Debug flag" ) boolean debug() ; @DefaultValue( "0" ) @Help( "Verbose flag" ) int verbose() ; @DefaultValue( "false" ) @Help( "Indicates a run that only prints out actions, " + "but does not perform them") boolean dryrun() ; @DefaultValue( "." ) @Help( "Directory to scan for class file" ) File dir() ; @DefaultValue( "false") @Help( "If true, write output to a .class.new file") boolean newout() ; @DefaultValue( "TimingPoints" ) @Help( "Control the mode of operation: TimingPoints, UpdateSchema, or TraceEnhance") ProcessingMode mode() ; @DefaultValue( "" ) @Help( "The timing point class name") String timingPointClass() ; @DefaultValue( "" ) @Help( "The directory in which to write the TimingPoint file") String timingPointDir() ; } private Arguments args ; private TimingInfoProcessor tip ; private class EnhancerFileAction implements Scanner.Action { private UnaryFunction<byte[],byte[]> ea ; public EnhancerFileAction( UnaryFunction<byte[],byte[]> ea ) { this.ea = ea ; } @Override public boolean evaluate( FileWrapper fw ) { try { util.info( 2, "Processing class " + fw.getName() ) ; byte[] inputData = fw.readAll() ; byte[] outputData = ea.evaluate( inputData ) ; if (outputData != null) { if (args.newout()) { String fname = fw.getName() + ".new" ; util.info( 1, "Writing to class file " + fname ) ; FileWrapper fwo = new FileWrapper( fname ) ; fwo.writeAll( outputData ) ; } else { util.info( 1, "Writing to class file " + fw.getName() ) ; fw.writeAll( outputData ) ; } } } catch (Exception exc) { util.info( 1, "Exception " + exc + " while processing class " + fw.getName() ) ; errorCount++ ; } // Always succeed, so we keep processing files after the first // error. return true ; } } private void generatePropertiesFile( Arguments args, Set<String> anames ) throws IOException { // Resource file that lists all MM annotations: // org.glassfish.tf.annotations.size=n // org.glassfish.tf.annotation.1=... // ... // org.glassfish.tf.annotation.n=... final FileWrapper fw = new FileWrapper( args.rf() ) ; fw.open( FileWrapper.OpenMode.WRITE_EMPTY ) ; try { fw.writeLine( "# Trace Facility Annotations" ) ; fw.writeLine( "# generated by EnhanceTool on " + new Date() ) ; fw.writeLine( "org.glassfish.tf.annotations.size=" + anames.size() ) ; int ctr=1 ; for (String str : anames) { String cname = str.replace( '/', '.' ) ; fw.writeLine( "org.glassfish.tf.annotation." + ctr + "=" + cname ) ; ctr++ ; } } finally { fw.close() ; } } private Scanner.Action makeIgnoreAction( final boolean trace ) { return new Scanner.Action() { @Override public String toString() { return "ignore action (ignore files that don't match)" ; } @Override public boolean evaluate(FileWrapper arg) { if (trace) { util.info( 1, "Skipping " + arg ) ; } return true ; } } ; } private void doScan( Arguments args, ActionFactory af, Scanner scanner, Scanner.Action classAct ) throws IOException { final Recognizer classRecognizer = af.getRecognizerAction() ; final Scanner.Action ignoreAction = makeIgnoreAction( args.debug() || args.verbose() > 2 ) ; classRecognizer.setDefaultAction( ignoreAction ) ; classRecognizer.addKnownSuffix( "class", classAct ) ; scanner.scan( classRecognizer ) ; } public void run( String[] strs ) { try { final ArgParser ap = new ArgParser( Arguments.class ) ; args = ap.parse( strs, Arguments.class ) ; util = new Util( args.debug(), args.verbose() ) ; final String tpname = args.timingPointClass() ; String pkg = "" ; String cname = tpname ; if (tpname.length() > 0) { int index = tpname.lastIndexOf('.') ; if (index > 0) { cname = tpname.substring( index + 1 ) ; pkg = tpname.substring( 0, index ) ; } } else { cname = "NotUsed" ; pkg = "no.package" ; } tip = new TimingInfoProcessor( cname, pkg ) ; final ActionFactory af = new ActionFactory( 0, args.dryrun() ) ; final Scanner scanner = new Scanner( 0, args.dir() ) ; AnnotationScannerAction annoAct = new AnnotationScannerAction( util, tip ) ; doScan( args, af, scanner, annoAct ) ; Set<String> anames = annoAct.getAnnotationNames() ; if (args.debug()) { util.info( 1, "MM Annotations: " + anames ) ; } generatePropertiesFile( args, anames ) ; Transformer ea = new Transformer( util, args.mode(), tip, anames ) ; final Scanner.Action act = new EnhancerFileAction( ea ) ; doScan( args, af, scanner, act ) ; Pair<String,TimerFactory> res = tip.getResult() ; if (!args.timingPointDir().equals( "" ) ) { TimerPointSourceGenerator.generateFile( args.timingPointDir(), res ); } } catch (Exception exc) { if (util == null) { util = new Util( true, 1 ) ; } util.info( 1, "Exception: " + exc ) ; if (args.debug()) { exc.printStackTrace() ; } } } public static void main( String[] strs ) { (new EnhanceTool()).run( strs ) ; if (errorCount > 0) { System.exit(errorCount); } } }