Index: lucene/src/test/org/apache/lucene/index/TestIndexWriter.java
===================================================================
--- lucene/src/test/org/apache/lucene/index/TestIndexWriter.java	(revision 1058274)
+++ lucene/src/test/org/apache/lucene/index/TestIndexWriter.java	(working copy)
@@ -135,7 +135,7 @@
         dir.close();
     }
 
-    private void addDoc(IndexWriter writer) throws IOException
+    public static void addDoc(IndexWriter writer) throws IOException
     {
         Document doc = new Document();
         doc.add(newField("content", "aaa", Field.Store.NO, Field.Index.ANALYZED));
Index: lucene/src/test/org/apache/lucene/index/TestSegmentListener.java
===================================================================
--- lucene/src/test/org/apache/lucene/index/TestSegmentListener.java	(revision 0)
+++ lucene/src/test/org/apache/lucene/index/TestSegmentListener.java	(revision 0)
@@ -0,0 +1,132 @@
+package org.apache.lucene.index;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.lucene.analysis.MockAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.SegmentListener.MergeEvent;
+import org.apache.lucene.index.SegmentListener.ReaderEvent;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestSegmentListener extends LuceneTestCase {
+  public void testMerge() throws Exception {
+    Directory dir = newDirectory();
+    IndexWriterConfig iwc = newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer());
+    
+    CompositeSegmentListener listeners = new CompositeSegmentListener();
+    EventHandler eventHandler = new EventHandler();
+    listeners.add(eventHandler);
+    iwc.setSegmentListeners(listeners);
+    IndexWriter writer = new IndexWriter(dir, iwc);
+    for (int x=0; x < 10; x++) {
+      TestIndexWriter.addDoc(writer);
+    }
+    writer.commit();
+    for (int x=0; x < 10; x++) {
+      TestIndexWriter.addDoc(writer);
+    }
+    writer.commit();
+    writer.optimize();
+    
+    assertTrue(eventHandler.init);
+    assertTrue(eventHandler.start);
+    assertTrue(eventHandler.complete);
+    
+    writer.close();
+    dir.close();
+  }
+  
+  public void testMergeAbort() throws Exception {
+    Directory dir = newDirectory();
+    IndexWriterConfig iwc = newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer());
+    ((LogMergePolicy)iwc.getMergePolicy()).setMergeFactor(2);
+    CompositeSegmentListener listeners = new CompositeSegmentListener();
+    EventHandler eventHandler = new EventHandler();
+    listeners.add(eventHandler);
+    iwc.setSegmentListeners(listeners);
+    MockIndexWriterMergeAbort writer = new MockIndexWriterMergeAbort(dir, iwc);
+    for (int x=0; x < 10; x++) {
+      TestIndexWriter.addDoc(writer);
+    }
+    writer.commit();
+    for (int x=0; x < 10; x++) {
+      TestIndexWriter.addDoc(writer);
+    }
+  
+    boolean hitEx = false;
+    try {
+      writer.commit();
+      fail();
+    } catch (RuntimeException th) {
+      hitEx = true;
+    }
+    assertTrue(hitEx);
+    assertTrue(eventHandler.init);
+    assertTrue(eventHandler.start);
+    assertTrue(eventHandler.abort);
+    assertFalse(eventHandler.complete);
+    
+    writer.close();
+    dir.close();
+  }
+
+  private static final class MockIndexWriterMergeAbort extends IndexWriter {
+    public MockIndexWriterMergeAbort(Directory dir, IndexWriterConfig conf) throws IOException {
+      super(dir, conf);
+    }
+
+    boolean doFail = true;
+
+    @Override
+    boolean testPoint(String name) {
+      if (doFail && name.equals("startCommitMerge"))
+        throw new RuntimeException("intentionally failing at startCommitMerge");
+      return true;
+    }
+  }
+  
+  public class EventHandler extends SegmentListener {
+    boolean init = false;
+    boolean start = false;
+    boolean complete = false;
+    boolean abort = false;
+    
+    public void readerEvent(ReaderEvent event) {
+    }
+    
+    public void mergeEvent(MergeEvent event) {
+      // System.out.println("mergeEvent:"+event);
+      if (event.type.equals(MergeEvent.Type.INIT)) {
+        init = true;
+      }
+      if (event.type.equals(MergeEvent.Type.START)) {
+        start = true;
+      }
+      if (event.type.equals(MergeEvent.Type.COMPLETE)) {
+        complete = true;
+      }
+      if (event.type.equals(MergeEvent.Type.ABORT)) {
+        abort = true;
+      }
+    }
+  }
+}
Index: lucene/src/java/org/apache/lucene/index/CompositeSegmentListener.java
===================================================================
--- lucene/src/java/org/apache/lucene/index/CompositeSegmentListener.java	(revision 0)
+++ lucene/src/java/org/apache/lucene/index/CompositeSegmentListener.java	(revision 0)
@@ -0,0 +1,53 @@
+package org.apache.lucene.index;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.index.SegmentListener.MergeEvent;
+import org.apache.lucene.index.SegmentListener.ReaderEvent;
+
+public class CompositeSegmentListener {
+  private List<SegmentListener> listeners = new ArrayList<SegmentListener>();
+  
+  public void add(SegmentListener listener) {
+    listeners.add(listener);
+  }
+  
+  @Override
+  public String toString() {
+    return "CompositeSegmentListener [listeners=" + listeners + "]";
+  }
+
+  public boolean any() {
+    return listeners.size() > 0;
+  }
+  
+  public void readerEvent(ReaderEvent event) {
+    for (SegmentListener listener : listeners) {
+      listener.readerEvent(event);
+    }
+  }
+  
+  public void mergeEvent(MergeEvent event) {
+    for (SegmentListener listener : listeners) {
+      listener.mergeEvent(event);
+    }
+  }
+}
Index: lucene/src/java/org/apache/lucene/index/SegmentListener.java
===================================================================
--- lucene/src/java/org/apache/lucene/index/SegmentListener.java	(revision 0)
+++ lucene/src/java/org/apache/lucene/index/SegmentListener.java	(revision 0)
@@ -0,0 +1,90 @@
+package org.apache.lucene.index;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+
+public abstract class SegmentListener {
+  public abstract void readerEvent(ReaderEvent event);
+  
+  public abstract void mergeEvent(MergeEvent event);
+  
+  public static class ReaderEvent {
+    public final long time;
+    public final Type type;
+    public final SegmentReader[] readers;
+    
+    public enum Type {
+      CLONE, OPEN, CLOSE
+    }
+    
+    public ReaderEvent(long time, Type type, SegmentReader[] readers) {
+      this.time = time;
+      this.type = type;
+      this.readers = readers;
+    }
+  }
+  
+  public static class MergeEvent {
+    public enum Type {
+      INIT, START, COMPLETE, ABORT
+    }
+    public final Type type;
+    public final long initTime;
+    public final Long startTime;
+    public final Long completeTime;
+    public final SegmentInfos segments;
+    public final SegmentInfo mergeInfo;
+    MergePolicy.OneMerge merge;
+    public final Throwable throwable;
+    
+    MergeEvent(Type type, MergePolicy.OneMerge merge) {
+      this.type = type;
+      this.merge = merge;
+      this.initTime = merge.initTime;
+      this.startTime = merge.startTime;
+      this.completeTime = merge.completeTime;
+      this.segments = merge.segments;
+      this.mergeInfo = merge.info;
+      this.throwable = merge.getException();
+    }
+    
+    @Override
+    public String toString() {
+      try {
+        return "MergeEvent [type=" + type + ", initTime=" + initTime
+            + ", startTime=" + startTime + ", completeTime=" + completeTime
+            + ", segments=" + segments + ", mergeInfo=" + mergeInfo
+            + ", merge=" + merge+", sourceSegmentsSize="+getSourceSegmentsSize(true)
+            + ", mergeSegmentsSize="+getMergeSegmentSize(true)+"]";
+      } catch (IOException ioe) {
+        throw new RuntimeException(ioe);
+      }
+    }
+    
+    public long getSourceSegmentsSize(boolean includeDocStores)
+        throws IOException {
+      return segments.totalSizeInBytes(includeDocStores);
+    }
+    
+    public long getMergeSegmentSize(boolean includeDocStores)
+        throws IOException {
+      return mergeInfo.sizeInBytes(includeDocStores);
+    }
+  }
+}
Index: lucene/src/java/org/apache/lucene/index/SegmentInfo.java
===================================================================
--- lucene/src/java/org/apache/lucene/index/SegmentInfo.java	(revision 1058274)
+++ lucene/src/java/org/apache/lucene/index/SegmentInfo.java	(working copy)
@@ -229,9 +229,11 @@
       if (sizeInBytesWithStore != -1) return sizeInBytesWithStore;
       sizeInBytesWithStore = 0;
       for (final String fileName : files()) {
-        // We don't count bytes used by a shared doc store against this segment
-        if (docStoreOffset == -1 || !IndexFileNames.isDocStoreFile(fileName)) {
-          sizeInBytesWithStore += dir.fileLength(fileName);
+        if (dir.fileExists(fileName)) {
+          // We don't count bytes used by a shared doc store against this segment
+          if (docStoreOffset == -1 || !IndexFileNames.isDocStoreFile(fileName)) {
+            sizeInBytesWithStore += dir.fileLength(fileName);
+          }
         }
       }
       return sizeInBytesWithStore;
@@ -509,7 +511,9 @@
       for(String ext : IndexFileNames.NON_STORE_INDEX_EXTENSIONS) {
         addIfExists(fileSet, IndexFileNames.segmentFileName(name, "", ext));
       }
-      segmentCodecs.files(dir, this, fileSet);
+      if (segmentCodecs != null) {
+        segmentCodecs.files(dir, this, fileSet);
+      }
     }
 
     if (docStoreOffset != -1) {
Index: lucene/src/java/org/apache/lucene/index/MergePolicy.java
===================================================================
--- lucene/src/java/org/apache/lucene/index/MergePolicy.java	(revision 1058274)
+++ lucene/src/java/org/apache/lucene/index/MergePolicy.java	(working copy)
@@ -75,6 +75,10 @@
     SegmentReader[] readers;        // used by IndexWriter
     SegmentReader[] readersClone;   // used by IndexWriter
     public final SegmentInfos segments;
+    public long sourceSegmentsSizeBytes;
+    Long startTime;
+    Long initTime;
+    Long completeTime;
     boolean aborted;
     Throwable error;
     boolean paused;
Index: lucene/src/java/org/apache/lucene/index/SegmentInfos.java
===================================================================
--- lucene/src/java/org/apache/lucene/index/SegmentInfos.java	(revision 1058274)
+++ lucene/src/java/org/apache/lucene/index/SegmentInfos.java	(working copy)
@@ -887,4 +887,12 @@
   public void changed() {
     version++;
   }
+  
+  public long totalSizeInBytes(boolean includeDocStores) throws IOException {
+    long total = 0;
+    for (SegmentInfo info : this) {
+      total += info.sizeInBytes(includeDocStores);
+    }
+    return total;
+  }
 }
Index: lucene/src/java/org/apache/lucene/index/IndexWriter.java
===================================================================
--- lucene/src/java/org/apache/lucene/index/IndexWriter.java	(revision 1058274)
+++ lucene/src/java/org/apache/lucene/index/IndexWriter.java	(working copy)
@@ -36,6 +36,7 @@
 import org.apache.lucene.document.Document;
 import org.apache.lucene.index.IndexWriterConfig.OpenMode;
 import org.apache.lucene.index.PayloadProcessorProvider.DirPayloadProcessor;
+import org.apache.lucene.index.SegmentListener.MergeEvent.Type;
 import org.apache.lucene.index.codecs.CodecProvider;
 import org.apache.lucene.index.codecs.DefaultSegmentInfosWriter;
 import org.apache.lucene.search.Query;
@@ -2942,6 +2943,13 @@
     // after our merge is done but while we are building the
     // CFS:
     mergingSegments.add(merge.info);
+    
+    merge.initTime = System.currentTimeMillis();
+    if (config.getSegmentListeners() != null) {
+      SegmentListener.MergeEvent event = 
+        new SegmentListener.MergeEvent(SegmentListener.MergeEvent.Type.INIT, merge);
+      config.getSegmentListeners().mergeEvent(event);
+    }
   }
 
   private void setDiagnostics(SegmentInfo info, String source) {
@@ -2983,6 +2991,20 @@
     }
 
     runningMerges.remove(merge);
+    
+    merge.completeTime = System.currentTimeMillis();
+    
+    if (config.getSegmentListeners() != null) {
+      if (!merge.aborted && merge.getException() == null) {
+        SegmentListener.MergeEvent event = 
+          new SegmentListener.MergeEvent(SegmentListener.MergeEvent.Type.COMPLETE, merge);
+        config.getSegmentListeners().mergeEvent(event);
+      } else {
+        SegmentListener.MergeEvent event = 
+          new SegmentListener.MergeEvent(SegmentListener.MergeEvent.Type.ABORT, merge);
+        config.getSegmentListeners().mergeEvent(event);
+      }
+    }
   }
 
   private synchronized void setMergeDocStoreIsCompoundFile(MergePolicy.OneMerge merge) {
@@ -3104,7 +3126,13 @@
       }
 
       merge.checkAborted(directory);
-
+      merge.startTime = System.currentTimeMillis();
+      
+      if (config.getSegmentListeners() != null) {
+        config.getSegmentListeners().mergeEvent(
+            new SegmentListener.MergeEvent(SegmentListener.MergeEvent.Type.START, merge));
+      }
+      
       // This is where all the work happens:
       mergedDocCount = merge.info.docCount = merger.merge();
 
Index: lucene/src/java/org/apache/lucene/index/IndexWriterConfig.java
===================================================================
--- lucene/src/java/org/apache/lucene/index/IndexWriterConfig.java	(revision 1058274)
+++ lucene/src/java/org/apache/lucene/index/IndexWriterConfig.java	(working copy)
@@ -128,6 +128,7 @@
   private int maxThreadStates;
   private boolean readerPooling;
   private int readerTermsIndexDivisor;
+  private CompositeSegmentListener segmentListeners;
   
   // required for clone
   private Version matchVersion;
@@ -603,6 +604,14 @@
     return readerTermsIndexDivisor;
   }
   
+  public void setSegmentListeners(CompositeSegmentListener segmentListeners) {
+    this.segmentListeners = segmentListeners;
+  }
+  
+  public CompositeSegmentListener getSegmentListeners() {
+    return segmentListeners;
+  }
+  
   @Override
   public String toString() {
     StringBuilder sb = new StringBuilder();
@@ -626,6 +635,7 @@
     sb.append("maxThreadStates=").append(maxThreadStates).append("\n");
     sb.append("readerPooling=").append(readerPooling).append("\n");
     sb.append("readerTermsIndexDivisor=").append(readerTermsIndexDivisor).append("\n");
+    sb.append("segmentListeners=").append(segmentListeners == null ? "null" : segmentListeners).append("\n");
     return sb.toString();
   }
 }
