mchr3k / org.intrace

Java tracing agent and live trace client
http://mchr3k.github.com/org.intrace/
73 stars 18 forks source link

enhancement request: instrument all implementation of a given interface #31

Open eostermueller opened 11 years ago

eostermueller commented 11 years ago

Vendors who make tools rely in java interfaces. WebServers servers like tomcat rely on interfaces in the package javax.servlet.http. Performance monitoring tools rely on javax.sql, javax.jms and many others.

If vendors could instrument the interfaces, they could use InTrace to track method invocations, regardless of the implementation. For example, they could instrument java.sql.PreparedStatement, and get notified of method invocation events regardless of which JDBC driver was in use.

This would be invaluable for comparing functionality and performance of various implementations of any spec/interface.

Details: Enable user to click on the "Classes" button in the InTrace UI and enter the name of an interface. InTrace agent would "instrument" all classes (and subclasses and subinterfaces) that implemented the given interface.

eostermueller commented 10 years ago

I took a first stab at implementing this and it didn't go too well.

In ClassTransformer.java, I used this:

  private boolean matches(String[] strs, String target)
  {
    for (String str : strs)
    {
      if (str.equals(MATCH_NONE))
      {
        continue;
      }
      else if (str.equals(MATCH_ALL) ||
               target.contains(str))
      {
        return true;
      }
      else if (isAssignableFrom(str, target))
      {
        return true;
      }
    }
    return false;
  }
  private boolean isAssignableFrom(String candidateInterface, String candidateImpl) {
    TraceHandler.INSTANCE.writeTraceOutput("DEBUG: Is impl class [" + candidateImpl + "] assignable from interface [" + candidateInterface + "]");
    System.out.println("XX DEBUG: Is impl class [" + candidateImpl + "] assignable from interface [" + candidateInterface + "]");
    boolean rc = false;
    Class klazCandidateInterface = null;
    Class klazCandidateImpl = null;
    try {
      klazCandidateInterface = Class.forName(candidateInterface);
      klazCandidateImpl = Class.forName(candidateImpl);
      if (klazCandidateInterface.isInterface() &&
          klazCandidateInterface.isAssignableFrom(klazCandidateImpl))
        {
           TraceHandler.INSTANCE.writeTraceOutput("DEBUG: isAssignableFrom=true");
           return true;
        }
    } catch (Exception e)
    { //any exception from above means the imple does not implement the interface.
        if ( e.getMessage().indexOf("TraceInterface")>0) {
             e.printStackTrace();
        }
      System.out.println("exception [" + e.getMessage() + "]");
    }
    TraceHandler.INSTANCE.writeTraceOutput("DEBUG: isAssignableFrom=false");
    System.out.println("DEBUG: isAssignableFrom=false");
    return false;
  }

Can't remember exactly what the problem was, but the code just seems wrong. Before a class is loaded, this class is evaluating whether it implements an interface. But it seems like the Java API must first load a class before you can make this determination.

So, I wonder if we should consider using this ASM-based library to determine if a class implements a particular interface: * http://software.clapper.org/javautil/ * http://software.clapper.org/javautil/api/org/clapper/util/classutil/ClassFinder.html

Would appreciate any thoughts on this. --Erik

mchr3k commented 10 years ago

I have pushed a commit with a quick implementation of this issue.

eostermueller commented 10 years ago

Martin, thanks. I gave that code a try and I needed a single line of code to get it to work. Would you give it a try, and commit if it looks good? Thanks, --Erik

From org/intrace/agent/ClassTransformer.java, the method isToBeConsideredForInstrumentation().

    // Don't modify classes which fail to match the regex
    if ((settings.getClassRegex() == null)
        || !matches(settings.getClassRegex(), className))
    {
      // Actually we should modify if any of the interfaces match the regex
      boolean matchedInterface = false;
      for(String klassInterface : getInterfaces(className, originalClassfile))
      {
        /**
          *  ##    ##                ##    ##                ##    ##                ##    ##               
          *  I had to add this next line of code to get this work work.
          *  ##    ##                ##    ##                ##    ##                ##    ##               
          */
        klassInterface = klassInterface.replace('/','.');   
        System.out.println("#@# Comparing [" + klassInterface + "]");
        if ((settings.getClassRegex() != null)
            && matches(settings.getClassRegex(), klassInterface))
        {
        System.out.println("matched interface successfully!");
          matchedInterface |= true;
        }
      }