Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

About this user

Sam McCall

« Newer Snippets
Older Snippets »
Showing 1-7 of 7 total  RSS 

Open an arbitrary number of resources safely in ruby

I'm too lazy to work out what happens if I try
filenames.map {|f| File.open(f) }
and the thirteenth file doesnt exist, but I bet I don't like it.

module Enumerable
  # Example:
  # ['a','b'].with_files {|f,g| ... }
  # is the same as
  # File.open('a') {|f| File.open('b') {|g| ... } }
  # You can specify modes with
  # [['a', 'rb'], ['b', 'w']].with_files ...
  def with_files(
      meth = File.method(:open),
      offset=0,
      inplace=false,
      &block
  )
    if inplace then
      if offset >= length then
        yield self
      else
        fname,mode = *self[offset]
        File.open(fname,mode) {|f| 
          self[offset] = f
          self.with_files(meth,offset+1,true,&block)
        }
      end
    else
      dup.with_files(meth,offset,true,&block)
    end
  end
end

procalyzer - analyze proc frequency from WoW combat log

# Usage:
# File.open('Logs/WowCombatLog.txt') {|log| 
#    Procalyzer::procalyze(log,"Light's Ward",&Procalyzer::PLAYER_SWING_OR_SPELL).dump
# }

require 'time'
require 'csv'

Stats = Struct.new(:min,:max,:n,:sx,:sx2)

# calculate mean, stdev, etc for an array of numbers
class Stats 
  def initialize(population)
    self.n = 0
    self.sx = self.sx2 = 0.0
    population.each { |x|
      self.n += 1
      self.sx += x
      self.sx2 += x*x;
      self.min = x if self.min.nil? or x < self.min
      self.max = x if self.max.nil? or x > self.max
    }
  end

  def mean
    sx/n
  end
  
  def variance
    sx2/n - (sx/n)**2
  end
 
  def stdev
    variance ** 0.5
  end

  def to_s
    "#{mean} (dev: #{stdev}, range: #{min} - #{max}, n: #{n})"
  end
end

module Procalyzer
	# wow 2.4 log entry
	LogEntry = Struct.new(:time,:message,:actor_guid,:actor_name,:actor_id,:target_guid,:target_name,:target_id,:rest)

	# parse a log into a list of LogEntries.
	# if block given, yields each entry and returns a list of the result
	# else resturns a list of entries
	def self.parse_log(stream) 
		lines = []
		while line=stream.gets
			entry = LogEntry.new
			line =~ %r{^(\d+)/(\d+) (\d\d):(\d\d):(\d\d).(\d\d\d) (.*)$} or raise "Couldnt parse line: #{line}"
			entry.time = Time.local(Time.new.year, $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, 1000*$6.to_i)
			entry.message, 
				entry.actor_guid, entry.actor_name, entry.actor_id, 
				entry.target_guid, entry.target_name, entry.target_id, 
				*rest = *CSV.parse_line($7.strip)
			entry.rest = rest
			if block_given?
				lines << yield(entry)
			else
				lines << entry
			end
		end
		lines
	end

	# Scans a logfile, runs event handlers for events matching certain filters
	class EventScanner
		def initialize
			@filters = []
			@start = []
			@end = []
		end
	
		# add a handler to be run for events matching filter
		def on(filter, &block)
			@filters << [filter,block]
		end
		# add a handler at the start
		def start(&block)
			@start << block
		end
		# add a handler at the end
		def end(&block)
			@end << block
		end
	
		# scan the stream and run handlers
		def run(file) 
			@start.each {|s| s.call() }
			Procalyzer::parse_log(file) {|entry|
				@filters.each {|filter,block|
					block.call(entry) if filter[entry]
				}
			}
			@end.each {|s| s.call() }
		end
	end

	# Container for analysis results
	# durations is a list of the proc lengths found
	# intervals is the list of times from one proc to the next
	# cooldown is the estimated or specified internal cooldown
	# procs is a list of 1 or 0 entries specifying whether each eligible event procced the aura
	ProcResults = Struct.new(:durations, :intervals, :cooldown, :procs)
	class ProcResults
		def initialize
			self.durations=[]
			self.intervals=[]
			self.procs=[]
		end
		# print results in a pretty human readable form
		def dump(out=$stdout)
			if self.durations.empty?
				out.puts "Proc did not occur."
			elsif self.intervals.empty?
				out.puts "Proc occurred once only."
			else
				dur_stats = Stats.new(durations)
				out.puts "Duration: #{dur_stats}"
				if dur_stats.stdev/dur_stats.mean > 0.1
					out.puts "Standard deviation of duration is more than 10%."
					out.puts "This can indicate a proc with no cooldown that refreshes itself."
				end
				interval_stats = Stats.new(intervals)
				out.puts "Interval: #{interval_stats}"
				out.printf("Uptime: %.2f%%\n", 100.0*(dur_stats.sx - durations[-1])/interval_stats.sx)
				out.puts "Internal cooldown: #{cooldown}"
				proc_stats = Stats.new(procs)			
				out.printf("Proc chance: %.2f%% (%d/%d)\n", 100*proc_stats.mean, proc_stats.sx.to_i, proc_stats.n)
			end
		end
	end

	# Analyse a log file, looking for a self-buff triggered by an event.
	# Assume it has a fixed % to proc unless it is on cooldown (or already active, 
	# in which case it could theoretically proc but won't slow in the log)
	# we do this in two phases:
	# 1) measure proc durations and intervals, and estimate cooldown if it's not specified
	#    (just minimum interval rounded down to nearest second)
	# 2) count eligible proccing events (ones outside cooldown) and see which procced it
	# file is a seekable stream containing the log
	# auraname is the buff name like "Haste"
	# cooldown is the internal cooldown if known (else will be estimated)
	# proc_trigger is a filter block for proc events (takes an event, returns true if the event can proc
	#   the aura, assuming the aura isnt on cooldown or already up)
	# returns a procresults object
	def self.procalyze(file, auraname, cooldown=nil, &proc_trigger) 
		results = ProcResults.new
		results.cooldown=cooldown

		scanner = EventScanner.new
	
		start = nil
		last = nil
	
		# Aura start: record start time and interval
		scanner.on(proc {|e| e.message == "SPELL_AURA_APPLIED" and e.rest[1] == auraname} ) {|evt|
			start = evt.time.to_f
			results.intervals << (start-last) if last
			last = start
		}
		# Aura finish: record duration
		scanner.on(proc {|e| e.message == "SPELL_AURA_REMOVED" and e.rest[1] == auraname} ) {|evt|
			if start
				duration = evt.time.to_f - start
				results.durations << duration
			end
		}
		scanner.run(file)

		return results if results.intervals.length==0

		# Estimate cooldown if not given	
		if results.cooldown.nil?
			interval_stats = Stats.new(results.intervals)
			duration_stats = Stats.new(results.durations)
			if(duration_stats.stdev/duration_stats.mean > 0.1)
				results.cooldown = 0.0
			else
				results.cooldown = interval_stats.min.floor
			end
			puts "Using cooldown = #{results.cooldown}" if $DEBUG
		end
	
		file.pos = 0 # restart file
		last_swing_time = nil
		last_swing_could_proc = false
		aura_up = false
		start = nil
		scanner = EventScanner.new

		start_swing = proc{|evt| last_swing_time = evt.time.to_f; last_swing_count_proc = true  }
		end_swing = proc{
			if(last_swing_could_proc)
				puts "Possible proc: #{last_swing_time}" if $DEBUG
				results.procs << (aura_up ? 1 : 0)
			end
			last_swing_could_proc = false
			last_swing_time = nil
		}

		# Aura start, start cooldown timer and set aura_up
		scanner.on(proc {|e| e.message == "SPELL_AURA_APPLIED" and e.rest[1] == auraname} ) {|evt|
			puts "Aura start: #{evt.time.to_f}" if $DEBUG
			start = evt.time.to_f
			aura_up = true
			end_swing.call()
		}
		# Aura end, clear aura_up
		scanner.on(proc {|e| e.message == "SPELL_AURA_REMOVED" and e.rest[1] == auraname} ) {|evt|
			aura_up = false
		}
		# Proc trigger event - eg a melee swing
		scanner.on(proc_trigger) {|evt|
			# first, this is the point where we confirm the previous swing didnt proc the aura
			end_swing.call()

			# record details of this swing, incl whether it could proc
			time_since_last = (start.nil?) ? 99999.0 : (evt.time.to_f - start)
			last_swing_could_proc = (not aura_up) && (time_since_last > results.cooldown)
			last_swing_time = evt.time.to_f
		}
		# at the end of the log file, confirm that the last swing didnt proc the aura
		scanner.end(&end_swing) 

		scanner.run(file)
		results
	end

	# actor_id for the player
	PLAYER_ID = "0x511"

	# predefined filters for common procs
	PLAYER_SWING = proc{|evt| evt.actor_id == PLAYER_ID and evt.message="SWING_DAMAGE" }
	PLAYER_SWING_OR_SPELL = proc{|evt| evt.actor_id == PLAYER_ID and (evt.message=="SWING_DAMAGE" or evt.message=="SPELL_DAMAGE") }
	PLAYER_SPELL_DAMAGE = proc{|evt| evt.actor_id == PLAYER_ID and evt.message=="SPELL_DAMAGE" }
        PLAYER_RANGED_OR_SPELL = proc{|evt| evt.actor_id == PLAYER_ID and (evt.message=="RANGE_DAMAGE" or evt.message=="SPELL_DAMAGE") }
end

Microbenchmark for Bridge vs reflection vs direct access

import java.lang.reflect.Method;

public class TimingTest {
	public interface IFoo { public void foo(); }
	public interface PubFoo { public void foo(); }
	public static class Impl implements PubFoo { public void foo(){} }

	static final int COUNT = 10000000;
	public static void main(String[] args) throws Exception {
		Impl impl = new Impl();
		int count;

		startTiming("Bridge (including BCEL)");
		IFoo ifoo = Bridge.expose(impl, IFoo.class);
		stopTiming();
		startTiming("Bridge (BCEL loaded)");
		Bridge.expose(impl, PubFoo.class);
		stopTiming();

		System.out.println(COUNT+" virtual invocations, warm");

		startTiming("Direct");
		for(int i=0; i<COUNT; i++)
			impl.foo();
		stopTiming();

		Method m = impl.getClass().getMethod("foo",new Class[0]);
		startTiming("Reflection");
		for(int i=0; i<COUNT; i++)
			m.invoke(impl);
		stopTiming();

		startTiming("Bridge");
		for(int i=0; i<COUNT; i++)
			ifoo.foo();
		stopTiming();


		System.out.println("1 virtual invocation on "+COUNT+" objects");

		startTiming("Direct");
		for(int i=0; i<COUNT; i++)
			impl.foo();
		stopTiming();

		startTiming("Reflection");
		Method tmpM = null;
		Class[] no_args = new Class[0];
		for(int i=0; i<COUNT; i++) {
			if(tmpM == null || tmpM.getDeclaringClass() != impl.getClass())
				tmpM = impl.getClass().getMethod("foo",no_args);
			tmpM.invoke(impl);
		}
		stopTiming();

		startTiming("Bridge");
		for(int i=0; i<COUNT; i++) {
			IFoo temp = Bridge.expose(impl, IFoo.class);
		}
		stopTiming();

		
		System.out.println(COUNT+" interface invocations, warm");
		PubFoo pfoo = (PubFoo)impl;
		Method mi = impl.getClass().getInterfaces()[0].getMethod("foo", new Class[]{});
		IFoo iifoo = Bridge.expose(impl, PubFoo.class, IFoo.class);

		startTiming("Direct");
		for(int i=0; i<COUNT; i++)
			pfoo.foo();
		stopTiming();

		startTiming("Reflection");
		for(int i=0; i<COUNT; i++)
			mi.invoke(impl);
		stopTiming();

		startTiming("Bridge");
		for(int i=0; i<COUNT; i++)
			iifoo.foo();
		stopTiming();
		
	}
	static long timer;
	static void startTiming(String task) {
		System.out.printf("  * %12s - ", task);
		System.out.flush();
		timer = System.currentTimeMillis();
	}
	static void stopTiming() {
		long time = System.currentTimeMillis() - timer;
		System.out.println(time+" ms");
	}
}

Exposing an interface at runtime using a proxy class

Force an object expose an interface at runtime, through a generated proxy class. It must implement all required methods.
Requires BCEL.

UPDATED: does verification at bridge generation time.
expose(Object, Class) will search for the correct public target type to use.
UPDATED: performance hacks.

Example usage:
public class BridgeTest {
  public interface CharList {
    public int length();
    public char charAt(int i);
  }
  void print(CharList l) {
    for(int i=0; i<l.length(); i++)
      System.out.print(l.charAt(i));
  }
  public static void main(String args[]) {
    CharList list = Bridge.expose("Hello world\n", CharList.class);
    print(list);
  }
}


Bridge.java:
/* Make an object expose an interface at runtime */

import org.apache.bcel.generic.*;
import org.apache.bcel.Constants;

import java.lang.reflect.Method;
import java.util.*;

public abstract class Bridge<C,I> {
	protected C __target; // the implementation of the interface
	private final void __init(Object target) { this.__target = (C)target; }

	// cache already-generated bridges
	private static HashMap<Class, HashMap<Class, Class>> cache 
		= new HashMap<Class, HashMap<Class, Class>>();
	// allow injection of classes from byte arrays
	private static InjectingClassLoader loader = new InjectingClassLoader();

	private static final void cacheSet(Class key1, Class key2, Class value) {
		HashMap<Class,Class> intermediate = cache.get(key1);
		if(intermediate == null)
			cache.put(key1, intermediate = new HashMap<Class,Class>());
		intermediate.put(key2, value);
	}
	private static final Class cacheGet(Class key1, Class key2) {
		HashMap<Class,Class> intermediate = cache.get(key1);
		if(intermediate == null)
			return null;
		return intermediate.get(key2);
	}

	// returns [ [ifaceMethods...] [fromMethods...] ]
	private static Method[][] getMapping(Class from, Class iface) throws NoSuchMethodException,IllegalAccessException {
		if(!iface.isInterface())
			throw new IllegalArgumentException(iface.getName()+" is not an interface");
		if(!java.lang.reflect.Modifier.isPublic(from.getModifiers()))
			throw new IllegalAccessException(from.getName()+" is not public");
		java.lang.reflect.Method[][] map = new java.lang.reflect.Method[2][];
		map[0] = iface.getMethods();
		map[1] = new java.lang.reflect.Method[map[0].length];

		for(int i=0;i<map[0].length;i++) {
			try {
				Method match = from.getMethod(map[0][i].getName(), map[0][i].getParameterTypes());
				if(!map[0][i].getReturnType().isAssignableFrom(match.getReturnType()))
					throw new NoSuchMethodException("Return type "+match.getReturnType().getName()+
						" of "+toString(match)+" is not compatible with return type "+
						map[0][i].getReturnType().getName()+" of "+toString(map[0][i]));
				map[1][i] = match;
			} catch (NoSuchMethodException e) {
				throw new NoSuchMethodException("Couldn't find "+toString(map[0][i])+" in "+
					from.getName());
			}			
		}
		return map;
	}
	private static String toString(Method m) {
		StringBuffer sb = new StringBuffer(m.getDeclaringClass().getName());
		sb.append(".").append(m.getName()).append("(");
		boolean first=true;
		for(Class c : m.getParameterTypes()) {
			if(!first)
				sb.append(", ");
			sb.append(c.getName());	
			first=false;
		}
		sb.append(")");
		return sb.toString();
	}

	private static <C,I> Class<? extends Bridge<C,I>> create(Class<C> from, Class<I> iface) {
		try {
			Method[][] map = getMapping(from, iface);

			String bridgeName = "Bridge_"+from.getSimpleName()+"_"+iface.getSimpleName();
			// public class Bridge_Thing_Mungible extends Bridge<Thing,Mungible> implements Mungible
			ClassGen cg = new ClassGen(
				bridgeName,
				Bridge.class.getName(), // superclass name
				"<generated>", // source file name
				Constants.ACC_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SUPER, // flags
				new String [] { iface.getName() } // interfaces
			);

			ConstantPoolGen cpg = cg.getConstantPool();
			InstructionFactory factory = new InstructionFactory(cg, cpg);

			ReferenceType targetType = (ReferenceType)Type.getType(from);

			for(int i=0; i<map[0].length; i++) {
				Method imeth = map[0][i];
				Method fmeth = map[1][i];

				Type 	ireturnType = Type.getType(imeth.getReturnType()),
					freturnType = Type.getType(fmeth.getReturnType());
				Class[]	iargs = imeth.getParameterTypes(), 
					fargs = fmeth.getParameterTypes();
				Type[] 	iargsT = new Type[iargs.length],
					fargsT = new Type[fargs.length];
				String[] argNames = new String[iargs.length];
				for(int j=0; j<iargs.length; j++) {
					iargsT[j] = Type.getType(iargs[j]);
					fargsT[j] = Type.getType(fargs[j]);
					argNames[j] = "arg"+i;
				}

				InstructionList il = new InstructionList();
				// public int munge(Object arg0, int arg1) {
				MethodGen mg = new MethodGen(
					Constants.ACC_PUBLIC,
					ireturnType,
					iargsT,
					argNames,
					imeth.getName(),
					bridgeName,
					il, cpg);

				// (Thing)this.__target
				il.append(new ALOAD(0));
				il.append(factory.createFieldAccess(
					Bridge.class.getName(), 
					"__target", 
					Type.OBJECT, 
					Constants.GETFIELD));
				il.append(factory.createCheckCast(targetType));
				
				// .munge(arg0, arg1);
				int position = 1;
				for(int j=0;j<iargs.length;j++) {
					il.append(factory.createLoad(iargsT[j], position));
					position += iargsT[j].getSize();
				}
				il.append(factory.createInvoke(
					from.getName(), 
					fmeth.getName(), 
					freturnType, 
					fargsT, 
					from.isInterface() ? Constants.INVOKEINTERFACE : Constants.INVOKEVIRTUAL));

				// return (last result, if any)
				il.append(factory.createReturn(ireturnType));

				mg.setMaxStack();
				cg.addMethod(mg.getMethod());
				il.dispose(); // Allow instruction handles to be reused
			}

			// public Bridge_Thing_Mungible() { super(); }
			cg.addEmptyConstructor(Constants.ACC_PUBLIC);

			try { cg.getJavaClass().dump("proxy.class"); } catch(Exception e) { throw new RuntimeException(e); }

			byte[] classData = cg.getJavaClass().getBytes();
			Class c = loader.load(cg.getClassName(), classData);
			return (Class<Bridge<C,I>>)c;
		} catch (IllegalAccessException e) { 
			throw new BridgeFailure(from, iface, e); 
		} catch (NoSuchMethodException e) { 
			throw new BridgeFailure(from, iface, e); 
		}
	}
	private static <C,I> Class<? extends Bridge<C,I>> get(Class<C> from, Class<I> iface) {
//		Pair<Class,Class> key = new Pair<Class,Class>(from, iface);
		Class<? extends Bridge<C,I>> bridgeClass = (Class<? extends Bridge<C,I>>)cacheGet(from, iface);
		if(bridgeClass == null) {
			bridgeClass = create(from, iface);
			cacheSet(from, iface, bridgeClass);
		}
		return bridgeClass;
	}

	// Expose interface iface by creating a proxy. 
	// the type of target must be from, a subclass of from, or a class implementing from.
	// from must be public, and must expose all methods in iface.
	public static <I> I expose(Object target, Class from, Class<I> iface) {
		try {
			Class<? extends Bridge<?,I>> c = Bridge.get(from, iface);
			Bridge<?,I> proxy = c.newInstance();
			proxy.__init(target);
			return (I)proxy;
		} catch (InstantiationException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}
	}
	
	// Expose interface iface by creating a proxy. 
	// Tries expose(target, target.getClass(), iface) first.
	// then works up the class hierarchy. If doesn't find a public superclass 
	// that exposes all methods, it tries interfaces.
	// if you know in advance the class or interface that exposes the needed methods,
	// use expose(target, Class.forName("ExposingClass"), iface).
	public static <I> I expose(Object target, Class<I> iface) {
		Class tClass = target.getClass();

		Class from = tClass;
		do {			
			try {
				I result = expose(target, from, iface);
				if(tClass != from) // if tClass == tFrom then get() sets the cache
					cacheSet(tClass, iface, result.getClass());
				return result;
			} catch (BridgeFailure e) {
				Throwable cause = e.getCause();
				if(cause instanceof NoSuchMethodException) {
					// have traced superclass up until the interface isn't satisfied.
					// try interfaces and then give up.
					// in the case of a private implementation of a public interface, 
					// this allows you to subset the interface.
					Class[] targetIfaces = target.getClass().getInterfaces();
					for(Class targetIface : targetIfaces) 
						try {
							I result = expose(target, targetIface, iface);
							cacheSet(tClass, iface, result.getClass()); // perf hack
							return result;
							// ignore, reported exception is that for class hierarchy.
						} catch (BridgeFailure ex) {} 
					throw new BridgeFailure(target, iface, e);
				} else if(!(cause instanceof IllegalAccessException))
					throw e;
			}
			from = from.getSuperclass();
		} while(from != null);
		throw new RuntimeException("Object "+target+" ("+target.getClass().getName()+") has no public superclass");
	}
}
class BridgeFailure extends RuntimeException {
	public BridgeFailure(Class from, Class iface, Exception cause) {
		super("Could not map class "+from.getName()+" to interface "+iface.getName());
		initCause(cause);
	}
	public BridgeFailure(Object target, Class iface, Exception cause) {
		super("Could not map first public supertype of "