Index: src/java/org/apache/lucene/index/SegmentListener.java
===================================================================
--- src/java/org/apache/lucene/index/SegmentListener.java	(revision 0)
+++ src/java/org/apache/lucene/index/SegmentListener.java	(revision 0)
@@ -0,0 +1,57 @@
+package org.apache.lucene.index;
+
+import java.io.IOException;
+
+/**
+ * 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.
+ */
+
+public abstract class SegmentListener {
+  public abstract void mergeEvent(MergeEvent event);
+    
+  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 endTime;
+    public final SegmentInfos segments;
+    public final SegmentInfo mergeInfo;
+    // TODO: place ioContext here
+    // nocommit: should the segment readers go here also?
+    
+    MergeEvent(Type type, MergePolicy.OneMerge merge) {
+      this.type = type;
+      this.initTime = merge.initTime;
+      this.startTime = merge.startTime;
+      this.endTime = merge.endTime;
+      this.segments = merge.segments;
+      this.mergeInfo = merge.info;
+    }
+    
+    public long getSourceSegmentsSize(boolean includeDocStores) throws IOException {
+      // nocommit: cache this?
+      return segments.totalSizeInBytes(includeDocStores);
+    }
+    
+    public long getMergeSegmentSize(boolean includeDocStores) throws IOException {
+      // nocommit: cache this?
+      return mergeInfo.sizeInBytes(includeDocStores);
+    }
+  }
+}
Index: src/java/org/apache/lucene/index/MergePolicy.java
===================================================================
--- src/java/org/apache/lucene/index/MergePolicy.java	(revision 1057778)
+++ 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 endTime;
     boolean aborted;
     Throwable error;
     boolean paused;
Index: src/java/org/apache/lucene/index/SegmentInfos.java
===================================================================
--- src/java/org/apache/lucene/index/SegmentInfos.java	(revision 1057778)
+++ 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: src/java/org/apache/lucene/index/IndexWriter.java
===================================================================
--- src/java/org/apache/lucene/index/IndexWriter.java	(revision 1057778)
+++ 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;
@@ -2893,6 +2894,12 @@
       if (bufferedDeletes.applyDeletes(readerPool, segmentInfos, merge.segments)) {
         checkpoint();
       }
+      merge.initTime = System.currentTimeMillis();
+      if (config.getSegmentListener() != null) {
+        SegmentListener.MergeEvent event = 
+          new SegmentListener.MergeEvent(SegmentListener.MergeEvent.Type.INIT, merge);
+        config.getSegmentListener().mergeEvent(event);
+      }
       _mergeInit(merge);
       success = true;
     } finally {
@@ -2983,6 +2990,14 @@
     }
 
     runningMerges.remove(merge);
+    
+    merge.endTime = System.currentTimeMillis();
+    
+    if (config.getSegmentListener() != null) {
+      SegmentListener.MergeEvent event = 
+        new SegmentListener.MergeEvent(SegmentListener.MergeEvent.Type.COMPLETE, merge);
+      config.getSegmentListener().mergeEvent(event);
+    }
   }
 
   private synchronized void setMergeDocStoreIsCompoundFile(MergePolicy.OneMerge merge) {
@@ -3104,7 +3119,13 @@
       }
 
       merge.checkAborted(directory);
-
+      merge.startTime = System.currentTimeMillis();
+      
+      if (config.getSegmentListener() != null) {
+        config.getSegmentListener().mergeEvent(
+            new SegmentListener.MergeEvent(SegmentListener.MergeEvent.Type.START, merge));
+      }
+      
       // This is where all the work happens:
       mergedDocCount = merge.info.docCount = merger.merge();
 
Index: src/java/org/apache/lucene/index/IndexWriterConfig.java
===================================================================
--- src/java/org/apache/lucene/index/IndexWriterConfig.java	(revision 1057778)
+++ src/java/org/apache/lucene/index/IndexWriterConfig.java	(working copy)
@@ -128,6 +128,7 @@
   private int maxThreadStates;
   private boolean readerPooling;
   private int readerTermsIndexDivisor;
+  private SegmentListener segmentListener;
   
   // required for clone
   private Version matchVersion;
@@ -603,6 +604,14 @@
     return readerTermsIndexDivisor;
   }
   
+  public void setSegmentListener(SegmentListener segmentListener) {
+    this.segmentListener = segmentListener;
+  }
+  
+  public SegmentListener getSegmentListener() {
+    return segmentListener;
+  }
+  
   @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("segmentListener=").append(segmentListener == null ? "null" : segmentListener).append("\n");
     return sb.toString();
   }
 }
