You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by ne...@apache.org on 2022/09/23 16:15:56 UTC

[pinot] branch master updated: refine minion worker event observer to track finer grained progress for tasks (#9449)

This is an automated email from the ASF dual-hosted git repository.

nehapawar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 67390528f6 refine minion worker event observer to track finer grained progress for tasks (#9449)
67390528f6 is described below

commit 67390528f67d2dbcfe48e6cbb40bf874b6f1508a
Author: Xiaobing <61...@users.noreply.github.com>
AuthorDate: Fri Sep 23 09:15:48 2022 -0700

    refine minion worker event observer to track finer grained progress for tasks (#9449)
    
    * refine minion worker event observer to track finer grained progress for tasks
    
    * cr
    
    * cr
---
 .../api/resources/PinotTaskRestletResource.java    |  6 +-
 .../core/minion/PinotHelixTaskResourceManager.java | 10 +--
 .../minion/PinotHelixTaskResourceManagerTest.java  |  6 +-
 .../api/resources/PinotTaskProgressResource.java   | 12 ++--
 .../event/BaseMinionProgressObserverFactory.java   | 29 ++++----
 .../pinot/minion/event/MinionEventObservers.java   |  4 +-
 .../pinot/minion/event/MinionProgressObserver.java | 80 ++++++++++++++++++----
 .../minion/event/MinionProgressObserverTest.java   | 52 ++++++++++++++
 .../BaseMultipleSegmentsConversionExecutor.java    | 30 +++++++-
 .../tasks/BaseSingleSegmentConversionExecutor.java | 19 +++++
 .../ConvertToRawIndexTaskExecutor.java             |  1 +
 ...vertToRowIndexTaskProgressObserverFactory.java} | 20 ++----
 .../tasks/mergerollup/MergeRollupTaskExecutor.java |  8 ++-
 .../MergeRollupTaskProgressObserverFactory.java}   | 20 ++----
 .../minion/tasks/purge/PurgeTaskExecutor.java      |  1 +
 .../PurgeTaskProgressObserverFactory.java}         | 20 ++----
 .../RealtimeToOfflineSegmentsTaskExecutor.java     |  8 ++-
 ...fflineSegmentsTaskProgressObserverFactory.java} | 20 ++----
 .../SegmentGenerationAndPushTaskExecutor.java      |  1 -
 ...nerationAndPushTaskProgressObserverFactory.java | 16 +----
 .../mergerollup/MergeRollupTaskExecutorTest.java   |  2 +
 .../minion/tasks/purge/PurgeTaskExecutorTest.java  |  2 +
 .../RealtimeToOfflineSegmentsTaskExecutorTest.java |  9 +++
 23 files changed, 251 insertions(+), 125 deletions(-)

diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java
index fa610c1a9d..e0535be493 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResource.java
@@ -410,8 +410,9 @@ public class PinotTaskRestletResource {
 
   @GET
   @Path("/tasks/subtask/{taskName}/progress")
+  @Produces(MediaType.APPLICATION_JSON)
   @ApiOperation("Get progress of specified sub tasks for the given task tracked by worker in memory")
-  public Map<String, String> getSubtaskProgress(@Context HttpHeaders httpHeaders,
+  public String getSubtaskProgress(@Context HttpHeaders httpHeaders,
       @ApiParam(value = "Task name", required = true) @PathParam("taskName") String taskName,
       @ApiParam(value = "Sub task names separated by comma") @QueryParam("subtaskNames") @Nullable
           String subtaskNames) {
@@ -429,9 +430,10 @@ public class PinotTaskRestletResource {
     });
     int timeoutMs = _controllerConf.getMinionAdminRequestTimeoutSeconds() * 1000;
     try {
-      return _pinotHelixTaskResourceManager
+      Map<String, Object> progress = _pinotHelixTaskResourceManager
           .getSubtaskProgress(taskName, subtaskNames, _executor, _connectionManager, workerEndpoints, requestHeaders,
               timeoutMs);
+      return JsonUtils.objectToString(progress);
     } catch (UnknownTaskTypeException | NoTaskScheduledException e) {
       throw new ControllerApplicationException(LOGGER, "Not task with name: " + taskName, Response.Status.NOT_FOUND, e);
     } catch (Exception e) {
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManager.java
index c6ba0885e4..fb28be3115 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManager.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManager.java
@@ -463,7 +463,7 @@ public class PinotHelixTaskResourceManager {
     return taskConfigs;
   }
 
-  public synchronized Map<String, String> getSubtaskProgress(String taskName, @Nullable String subtaskNames,
+  public synchronized Map<String, Object> getSubtaskProgress(String taskName, @Nullable String subtaskNames,
       Executor executor, HttpConnectionManager connMgr, Map<String, String> workerEndpoints,
       Map<String, String> requestHeaders, int timeoutMs)
       throws Exception {
@@ -473,7 +473,7 @@ public class PinotHelixTaskResourceManager {
   }
 
   @VisibleForTesting
-  Map<String, String> getSubtaskProgress(String taskName, @Nullable String subtaskNames,
+  Map<String, Object> getSubtaskProgress(String taskName, @Nullable String subtaskNames,
       CompletionServiceHelper completionServiceHelper, Map<String, String> workerEndpoints,
       Map<String, String> requestHeaders, int timeoutMs)
       throws Exception {
@@ -504,7 +504,7 @@ public class PinotHelixTaskResourceManager {
             StringUtils.join(subtasksOnWorker, CommonConstants.Minion.TASK_LIST_SEPARATOR))));
     LOGGER.debug("Getting task progress with workerUrls: {}", workerUrls);
     // Scatter and gather progress from multiple workers.
-    Map<String, String> subtaskProgressMap = new HashMap<>();
+    Map<String, Object> subtaskProgressMap = new HashMap<>();
     CompletionServiceHelper.CompletionServiceResponse serviceResponse =
         completionServiceHelper.doMultiGetRequest(workerUrls, null, true, requestHeaders, timeoutMs);
     for (Map.Entry<String, String> entry : serviceResponse._httpResponses.entrySet()) {
@@ -526,8 +526,8 @@ public class PinotHelixTaskResourceManager {
       } else {
         String taskWorker = taskWorkerAndHelixState[0];
         String helixState = taskWorkerAndHelixState[1];
-        subtaskProgressMap.put(subtaskName, String
-            .format("No progress status from worker: %s but find status: %s tracked by Helix", taskWorker, helixState));
+        subtaskProgressMap.put(subtaskName,
+            String.format("No status from worker: %s. Got status: %s from Helix", taskWorker, helixState));
       }
     }
     // Raise error if any worker failed to report progress, with partial result.
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManagerTest.java
index 13b2805323..fd601a21bd 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManagerTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManagerTest.java
@@ -60,7 +60,7 @@ public class PinotHelixTaskResourceManagerTest {
     for (int i = 0; i < 3; i++) {
       subtaskNames[i] = taskName + "_" + i;
     }
-    Map<String, String> progress =
+    Map<String, Object> progress =
         mgr.getSubtaskProgress(taskName, StringUtils.join(subtaskNames, ','), httpHelper, workerEndpoints,
             Collections.emptyMap(), 1000);
     for (String subtaskName : subtaskNames) {
@@ -99,11 +99,11 @@ public class PinotHelixTaskResourceManagerTest {
     when(jobContext.getAssignedParticipant(anyInt())).thenReturn(workers[0], workers[1], workers[2]);
     when(jobContext.getPartitionState(anyInt())).thenReturn(helixStates[0], helixStates[1], helixStates[2]);
     when(jobContext.getPartitionSet()).thenReturn(subtaskIds);
-    Map<String, String> progress =
+    Map<String, Object> progress =
         mgr.getSubtaskProgress(taskName, StringUtils.join(subtaskNames, ','), httpHelper, workerEndpoints,
             Collections.emptyMap(), 1000);
     for (int i = 0; i < 3; i++) {
-      String taskProgress = progress.get(subtaskNames[i]);
+      String taskProgress = (String) progress.get(subtaskNames[i]);
       assertTrue(taskProgress.contains(helixStates[i].name()), subtaskNames[i] + ":" + taskProgress);
     }
   }
diff --git a/pinot-minion/src/main/java/org/apache/pinot/minion/api/resources/PinotTaskProgressResource.java b/pinot-minion/src/main/java/org/apache/pinot/minion/api/resources/PinotTaskProgressResource.java
index 8b62472056..c2260bec8e 100644
--- a/pinot-minion/src/main/java/org/apache/pinot/minion/api/resources/PinotTaskProgressResource.java
+++ b/pinot-minion/src/main/java/org/apache/pinot/minion/api/resources/PinotTaskProgressResource.java
@@ -31,14 +31,17 @@ import java.util.HashMap;
 import java.util.Map;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang.StringUtils;
 import org.apache.pinot.minion.event.MinionEventObserver;
 import org.apache.pinot.minion.event.MinionEventObservers;
 import org.apache.pinot.spi.utils.CommonConstants;
+import org.apache.pinot.spi.utils.JsonUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -57,15 +60,15 @@ public class PinotTaskProgressResource {
 
   @GET
   @Path("/tasks/subtask/progress")
+  @Produces(MediaType.APPLICATION_JSON)
   @ApiOperation("Get finer grained task progress tracked in memory for the given subtasks")
   @ApiResponses(value = {
       @ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 500, message = "Internal server error")
   })
-  public Object getSubtaskProgress(
+  public String getSubtaskProgress(
       @ApiParam(value = "Sub task names separated by comma") @QueryParam("subtaskNames") String subtaskNames) {
-
     try {
-      LOGGER.debug("Get progress for subtasks: {}", subtaskNames);
+      LOGGER.debug("Getting progress for subtasks: {}", subtaskNames);
       Map<String, Object> progress = new HashMap<>();
       for (String subtaskName : StringUtils.split(subtaskNames, CommonConstants.Minion.TASK_LIST_SEPARATOR)) {
         MinionEventObserver observer = MinionEventObservers.getInstance().getMinionEventObserver(subtaskName);
@@ -73,7 +76,8 @@ public class PinotTaskProgressResource {
           progress.put(subtaskName, observer.getProgress());
         }
       }
-      return progress;
+      LOGGER.debug("Got subtasks progress: {}", progress);
+      return JsonUtils.objectToString(progress);
     } catch (Exception e) {
       throw new WebApplicationException(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(
           String.format("Failed to get task progress for subtasks: %s due to error: %s", subtaskNames, e.getMessage()))
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java b/pinot-minion/src/main/java/org/apache/pinot/minion/event/BaseMinionProgressObserverFactory.java
similarity index 61%
copy from pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java
copy to pinot-minion/src/main/java/org/apache/pinot/minion/event/BaseMinionProgressObserverFactory.java
index 6edd11bf79..df9119287d 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java
+++ b/pinot-minion/src/main/java/org/apache/pinot/minion/event/BaseMinionProgressObserverFactory.java
@@ -16,29 +16,30 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.pinot.plugin.minion.tasks.segmentgenerationandpush;
+package org.apache.pinot.minion.event;
 
-import org.apache.pinot.core.common.MinionConstants;
-import org.apache.pinot.minion.event.MinionEventObserver;
-import org.apache.pinot.minion.event.MinionEventObserverFactory;
-import org.apache.pinot.minion.event.MinionProgressObserver;
 import org.apache.pinot.minion.executor.MinionTaskZkMetadataManager;
-import org.apache.pinot.spi.annotations.minion.EventObserverFactory;
 
 
-@EventObserverFactory
-public class SegmentGenerationAndPushTaskProgressObserverFactory implements MinionEventObserverFactory {
+/**
+ * Base factory for {@link MinionEventObserver}.
+ */
+public abstract class BaseMinionProgressObserverFactory implements MinionEventObserverFactory {
 
-  @Override
+  /**
+   * Initializes the task executor factory.
+   */
   public void init(MinionTaskZkMetadataManager zkMetadataManager) {
   }
 
-  @Override
-  public String getTaskType() {
-    return MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE;
-  }
+  /**
+   * Returns the task type of the event observer.
+   */
+  public abstract String getTaskType();
 
-  @Override
+  /**
+   * Creates a new task event observer.
+   */
   public MinionEventObserver create() {
     return new MinionProgressObserver();
   }
diff --git a/pinot-minion/src/main/java/org/apache/pinot/minion/event/MinionEventObservers.java b/pinot-minion/src/main/java/org/apache/pinot/minion/event/MinionEventObservers.java
index 8232f76960..440b38a787 100644
--- a/pinot-minion/src/main/java/org/apache/pinot/minion/event/MinionEventObservers.java
+++ b/pinot-minion/src/main/java/org/apache/pinot/minion/event/MinionEventObservers.java
@@ -58,8 +58,8 @@ public class MinionEventObservers {
   }
 
   private void startCleanup() {
-    if (_cleanupExecutor == null) {
-      LOGGER.info("No executor to cleanup task event observer");
+    if (_cleanupExecutor == null || _eventObserverCleanupDelayMs == 0) {
+      LOGGER.info("Configured to clean up task event observers immediately");
       return;
     }
     _cleanupExecutor.submit(() -> {
diff --git a/pinot-minion/src/main/java/org/apache/pinot/minion/event/MinionProgressObserver.java b/pinot-minion/src/main/java/org/apache/pinot/minion/event/MinionProgressObserver.java
index 4795e53c39..b20029f7a2 100644
--- a/pinot-minion/src/main/java/org/apache/pinot/minion/event/MinionProgressObserver.java
+++ b/pinot-minion/src/main/java/org/apache/pinot/minion/event/MinionProgressObserver.java
@@ -20,7 +20,12 @@ package org.apache.pinot.minion.event;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.List;
 import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
 import org.apache.pinot.core.minion.PinotTaskConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -29,52 +34,78 @@ import org.slf4j.LoggerFactory;
 /**
  * A minion event observer that can track task progress status in memory.
  */
+@ThreadSafe
 public class MinionProgressObserver extends DefaultMinionEventObserver {
   private static final Logger LOGGER = LoggerFactory.getLogger(MinionProgressObserver.class);
+  // TODO: make this configurable
+  private static final int DEFAULT_MAX_NUM_STATUS_TO_TRACK = 128;
 
-  private static volatile long _startTs;
-  private static volatile Object _lastStatus;
+  private final int _maxNumStatusToTrack;
+  private final Deque<StatusEntry> _lastStatus = new LinkedList<>();
+  private long _startTs;
+
+  public MinionProgressObserver() {
+    this(DEFAULT_MAX_NUM_STATUS_TO_TRACK);
+  }
+
+  public MinionProgressObserver(int maxNumStatusToTrack) {
+    _maxNumStatusToTrack = maxNumStatusToTrack;
+  }
 
   @Override
-  public void notifyTaskStart(PinotTaskConfig pinotTaskConfig) {
+  public synchronized void notifyTaskStart(PinotTaskConfig pinotTaskConfig) {
     _startTs = System.currentTimeMillis();
+    addStatus(_startTs, "Task started");
     super.notifyTaskStart(pinotTaskConfig);
   }
 
-  public void notifyProgress(PinotTaskConfig pinotTaskConfig, @Nullable Object progress) {
+  /**
+   * Invoked to update a minion task progress status.
+   *
+   * @param pinotTaskConfig Pinot task config
+   * @param progress progress status and its toString() returns sth meaningful.
+   */
+  public synchronized void notifyProgress(PinotTaskConfig pinotTaskConfig, @Nullable Object progress) {
     if (LOGGER.isDebugEnabled()) {
       LOGGER.debug("Update progress: {} for task: {}", progress, pinotTaskConfig.getTaskId());
     }
-    _lastStatus = progress;
+    addStatus(System.currentTimeMillis(), (progress == null) ? "" : progress.toString());
     super.notifyProgress(pinotTaskConfig, progress);
   }
 
   @Nullable
-  public Object getProgress() {
-    return _lastStatus;
+  public synchronized List<StatusEntry> getProgress() {
+    return new ArrayList<>(_lastStatus);
   }
 
   @Override
-  public void notifyTaskSuccess(PinotTaskConfig pinotTaskConfig, @Nullable Object executionResult) {
+  public synchronized void notifyTaskSuccess(PinotTaskConfig pinotTaskConfig, @Nullable Object executionResult) {
     long endTs = System.currentTimeMillis();
-    _lastStatus = "Task succeeded in " + (endTs - _startTs) + "ms";
+    addStatus(endTs, "Task succeeded in " + (endTs - _startTs) + "ms");
     super.notifyTaskSuccess(pinotTaskConfig, executionResult);
   }
 
   @Override
-  public void notifyTaskCancelled(PinotTaskConfig pinotTaskConfig) {
+  public synchronized void notifyTaskCancelled(PinotTaskConfig pinotTaskConfig) {
     long endTs = System.currentTimeMillis();
-    _lastStatus = "Task got cancelled after " + (endTs - _startTs) + "ms";
+    addStatus(endTs, "Task got cancelled after " + (endTs - _startTs) + "ms");
     super.notifyTaskCancelled(pinotTaskConfig);
   }
 
   @Override
-  public void notifyTaskError(PinotTaskConfig pinotTaskConfig, Exception e) {
+  public synchronized void notifyTaskError(PinotTaskConfig pinotTaskConfig, Exception e) {
     long endTs = System.currentTimeMillis();
-    _lastStatus = "Task failed in " + (endTs - _startTs) + "ms, with error:\n" + makeStringFromException(e);
+    addStatus(endTs, "Task failed in " + (endTs - _startTs) + "ms with error: " + makeStringFromException(e));
     super.notifyTaskError(pinotTaskConfig, e);
   }
 
+  private void addStatus(long ts, String progress) {
+    _lastStatus.addLast(new StatusEntry(ts, progress));
+    if (_lastStatus.size() > _maxNumStatusToTrack) {
+      _lastStatus.pollFirst();
+    }
+  }
+
   private static String makeStringFromException(Exception exp) {
     StringWriter expStr = new StringWriter();
     try (PrintWriter pw = new PrintWriter(expStr)) {
@@ -82,4 +113,27 @@ public class MinionProgressObserver extends DefaultMinionEventObserver {
     }
     return expStr.toString();
   }
+
+  public static class StatusEntry {
+    private final long _ts;
+    private final String _status;
+
+    public StatusEntry(long ts, String status) {
+      _ts = ts;
+      _status = status;
+    }
+
+    public long getTs() {
+      return _ts;
+    }
+
+    public String getStatus() {
+      return _status;
+    }
+
+    @Override
+    public String toString() {
+      return "StatusEntry{" + "_ts=" + _ts + ", _status=" + _status + '}';
+    }
+  }
 }
diff --git a/pinot-minion/src/test/java/org/apache/pinot/minion/event/MinionProgressObserverTest.java b/pinot-minion/src/test/java/org/apache/pinot/minion/event/MinionProgressObserverTest.java
new file mode 100644
index 0000000000..897f19423e
--- /dev/null
+++ b/pinot-minion/src/test/java/org/apache/pinot/minion/event/MinionProgressObserverTest.java
@@ -0,0 +1,52 @@
+/**
+ * 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.
+ */
+package org.apache.pinot.minion.event;
+
+import java.util.List;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+
+public class MinionProgressObserverTest {
+  @Test
+  public void testNotifyProgressStatus() {
+    MinionProgressObserver observer = new MinionProgressObserver(3);
+
+    observer.notifyTaskStart(null);
+    List<MinionProgressObserver.StatusEntry> progress = observer.getProgress();
+    assertEquals(progress.size(), 1);
+
+    observer.notifyProgress(null, "preparing input: A");
+    observer.notifyProgress(null, "preparing input: B");
+    observer.notifyProgress(null, "generating segment");
+    progress = observer.getProgress();
+    assertEquals(progress.size(), 3);
+
+    observer.notifyProgress(null, "uploading segment");
+    observer.notifyTaskError(null, new Exception("bad bug"));
+    progress = observer.getProgress();
+    assertEquals(progress.size(), 3);
+    MinionProgressObserver.StatusEntry entry = progress.get(0);
+    assertTrue(entry.getStatus().contains("generating"), entry.getStatus());
+    entry = progress.get(2);
+    assertTrue(entry.getStatus().contains("bad bug"), entry.getStatus());
+  }
+}
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/BaseMultipleSegmentsConversionExecutor.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/BaseMultipleSegmentsConversionExecutor.java
index d33921b080..9328316099 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/BaseMultipleSegmentsConversionExecutor.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/BaseMultipleSegmentsConversionExecutor.java
@@ -18,6 +18,7 @@
  */
 package org.apache.pinot.plugin.minion.tasks;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import java.io.File;
 import java.util.ArrayList;
@@ -42,6 +43,8 @@ import org.apache.pinot.common.utils.fetcher.SegmentFetcherFactory;
 import org.apache.pinot.core.common.MinionConstants;
 import org.apache.pinot.core.minion.PinotTaskConfig;
 import org.apache.pinot.minion.MinionConf;
+import org.apache.pinot.minion.event.MinionEventObserver;
+import org.apache.pinot.minion.event.MinionEventObservers;
 import org.apache.pinot.minion.exception.TaskCancelledException;
 import org.apache.pinot.spi.auth.AuthProvider;
 import org.apache.pinot.spi.utils.builder.TableNameBuilder;
@@ -65,6 +68,10 @@ public abstract class BaseMultipleSegmentsConversionExecutor extends BaseTaskExe
 
   protected MinionConf _minionConf;
 
+  // Tracking finer grained progress status.
+  protected PinotTaskConfig _pinotTaskConfig;
+  protected MinionEventObserver _eventObserver;
+
   public BaseMultipleSegmentsConversionExecutor(MinionConf minionConf) {
     _minionConf = minionConf;
   }
@@ -97,6 +104,8 @@ public abstract class BaseMultipleSegmentsConversionExecutor extends BaseTaskExe
   protected void preUploadSegments(SegmentUploadContext context)
       throws Exception {
     // Update the segment lineage to indicate that the segment replacement is in progress.
+    _eventObserver.notifyProgress(_pinotTaskConfig,
+        "Prepare to upload segments: " + context.getSegmentConversionResults().size());
     if (context.isReplaceSegmentsEnabled()) {
       List<String> segmentsFrom =
           Arrays.stream(StringUtils.split(context.getInputSegmentNames(), MinionConstants.SEGMENT_NAME_SEPARATOR))
@@ -114,6 +123,8 @@ public abstract class BaseMultipleSegmentsConversionExecutor extends BaseTaskExe
   protected void postUploadSegments(SegmentUploadContext context)
       throws Exception {
     // Update the segment lineage to indicate that the segment replacement is done.
+    _eventObserver.notifyProgress(_pinotTaskConfig,
+        "Finishing uploading segments: " + context.getSegmentConversionResults().size());
     if (context.isReplaceSegmentsEnabled()) {
       String lineageEntryId = (String) context.getCustomContext(CUSTOM_SEGMENT_UPLOAD_CONTEXT_LINEAGE_ENTRY_ID);
       SegmentConversionUtils.endSegmentReplace(context.getTableNameWithType(), context.getUploadURL(), lineageEntryId,
@@ -121,11 +132,18 @@ public abstract class BaseMultipleSegmentsConversionExecutor extends BaseTaskExe
     }
   }
 
+  // For tests only.
+  @VisibleForTesting
+  public void setMinionEventObserver(MinionEventObserver observer) {
+    _eventObserver = observer;
+  }
+
   @Override
   public List<SegmentConversionResult> executeTask(PinotTaskConfig pinotTaskConfig)
       throws Exception {
     preProcess(pinotTaskConfig);
-
+    _pinotTaskConfig = pinotTaskConfig;
+    _eventObserver = MinionEventObservers.getInstance().getMinionEventObserver(pinotTaskConfig.getTaskId());
     String taskType = pinotTaskConfig.getTaskType();
     Map<String, String> configs = pinotTaskConfig.getConfigs();
     String tableNameWithType = configs.get(MinionConstants.TABLE_NAME_KEY);
@@ -146,11 +164,15 @@ public abstract class BaseMultipleSegmentsConversionExecutor extends BaseTaskExe
       List<File> inputSegmentDirs = new ArrayList<>();
       for (int i = 0; i < downloadURLs.length; i++) {
         // Download the segment file
+        _eventObserver.notifyProgress(_pinotTaskConfig,
+            String.format("Downloading segment from: %s, %d/%d", downloadURLs[i], (i + 1), downloadURLs.length));
         File tarredSegmentFile = new File(tempDataDir, "tarredSegmentFile_" + i);
         LOGGER.info("Downloading segment from {} to {}", downloadURLs[i], tarredSegmentFile.getAbsolutePath());
         SegmentFetcherFactory.fetchAndDecryptSegmentToLocal(downloadURLs[i], tarredSegmentFile, crypterName);
 
         // Un-tar the segment file
+        _eventObserver.notifyProgress(_pinotTaskConfig,
+            String.format("Decompressing segment from: %s, %d/%d", downloadURLs[i], (i + 1), downloadURLs.length));
         File segmentDir = new File(tempDataDir, "segmentDir_" + i);
         File indexDir = TarGzCompressionUtils.untar(tarredSegmentFile, segmentDir).get(0);
         inputSegmentDirs.add(indexDir);
@@ -170,8 +192,12 @@ public abstract class BaseMultipleSegmentsConversionExecutor extends BaseTaskExe
 
       int numOutputSegments = segmentConversionResults.size();
       List<File> tarredSegmentFiles = new ArrayList<>(numOutputSegments);
+      int count = 1;
       for (SegmentConversionResult segmentConversionResult : segmentConversionResults) {
         // Tar the converted segment
+        _eventObserver.notifyProgress(_pinotTaskConfig, String
+            .format("Compressing segment: %s, %d/%d", segmentConversionResult.getSegmentName(), count++,
+                numOutputSegments));
         File convertedSegmentDir = segmentConversionResult.getFile();
         File convertedSegmentTarFile = new File(convertedTarredSegmentDir,
             segmentConversionResult.getSegmentName() + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION);
@@ -206,6 +232,8 @@ public abstract class BaseMultipleSegmentsConversionExecutor extends BaseTaskExe
         File convertedTarredSegmentFile = tarredSegmentFiles.get(i);
         SegmentConversionResult segmentConversionResult = segmentConversionResults.get(i);
         String resultSegmentName = segmentConversionResult.getSegmentName();
+        _eventObserver.notifyProgress(_pinotTaskConfig,
+            String.format("Uploading segment: %s, %d/%d", resultSegmentName, (i + 1), numOutputSegments));
 
         // Set segment ZK metadata custom map modifier into HTTP header to modify the segment ZK metadata
         SegmentZKMetadataCustomMapModifier segmentZKMetadataCustomMapModifier =
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/BaseSingleSegmentConversionExecutor.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/BaseSingleSegmentConversionExecutor.java
index dbd4ebe99c..bc440e0c5e 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/BaseSingleSegmentConversionExecutor.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/BaseSingleSegmentConversionExecutor.java
@@ -18,6 +18,7 @@
  */
 package org.apache.pinot.plugin.minion.tasks;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import java.io.File;
 import java.util.ArrayList;
@@ -38,6 +39,8 @@ import org.apache.pinot.common.utils.TarGzCompressionUtils;
 import org.apache.pinot.common.utils.fetcher.SegmentFetcherFactory;
 import org.apache.pinot.core.common.MinionConstants;
 import org.apache.pinot.core.minion.PinotTaskConfig;
+import org.apache.pinot.minion.event.MinionEventObserver;
+import org.apache.pinot.minion.event.MinionEventObservers;
 import org.apache.pinot.minion.exception.TaskCancelledException;
 import org.apache.pinot.spi.auth.AuthProvider;
 import org.apache.pinot.spi.utils.builder.TableNameBuilder;
@@ -54,6 +57,10 @@ import org.slf4j.LoggerFactory;
 public abstract class BaseSingleSegmentConversionExecutor extends BaseTaskExecutor {
   private static final Logger LOGGER = LoggerFactory.getLogger(BaseSingleSegmentConversionExecutor.class);
 
+  // Tracking finer grained progress status.
+  protected PinotTaskConfig _pinotTaskConfig;
+  protected MinionEventObserver _eventObserver;
+
   /**
    * Converts the segment based on the given task config and returns the conversion result.
    */
@@ -63,6 +70,8 @@ public abstract class BaseSingleSegmentConversionExecutor extends BaseTaskExecut
   @Override
   public SegmentConversionResult executeTask(PinotTaskConfig pinotTaskConfig)
       throws Exception {
+    _pinotTaskConfig = pinotTaskConfig;
+    _eventObserver = MinionEventObservers.getInstance().getMinionEventObserver(pinotTaskConfig.getTaskId());
     String taskType = pinotTaskConfig.getTaskType();
     Map<String, String> configs = pinotTaskConfig.getConfigs();
     String tableNameWithType = configs.get(MinionConstants.TABLE_NAME_KEY);
@@ -89,11 +98,13 @@ public abstract class BaseSingleSegmentConversionExecutor extends BaseTaskExecut
 
     try {
       // Download the tarred segment file
+      _eventObserver.notifyProgress(_pinotTaskConfig, "Downloading segment from: " + downloadURL);
       File tarredSegmentFile = new File(tempDataDir, "tarredSegment");
       LOGGER.info("Downloading segment from {} to {}", downloadURL, tarredSegmentFile.getAbsolutePath());
       SegmentFetcherFactory.fetchAndDecryptSegmentToLocal(downloadURL, tarredSegmentFile, crypterName);
 
       // Un-tar the segment file
+      _eventObserver.notifyProgress(_pinotTaskConfig, "Decompressing segment from: " + downloadURL);
       File segmentDir = new File(tempDataDir, "segmentDir");
       File indexDir = TarGzCompressionUtils.untar(tarredSegmentFile, segmentDir).get(0);
       if (!FileUtils.deleteQuietly(tarredSegmentFile)) {
@@ -109,6 +120,7 @@ public abstract class BaseSingleSegmentConversionExecutor extends BaseTaskExecut
           segmentConversionResult.getSegmentName(), segmentName);
 
       // Tar the converted segment
+      _eventObserver.notifyProgress(_pinotTaskConfig, "Compressing segment: " + segmentName);
       File convertedSegmentDir = segmentConversionResult.getFile();
       File convertedTarredSegmentFile =
           new File(tempDataDir, segmentName + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION);
@@ -164,6 +176,7 @@ public abstract class BaseSingleSegmentConversionExecutor extends BaseTaskExecut
           tableTypeParameter);
 
       // Upload the tarred segment
+      _eventObserver.notifyProgress(_pinotTaskConfig, "Uploading segment: " + segmentName);
       SegmentConversionUtils.uploadSegment(configs, httpHeaders, parameters, tableNameWithType, segmentName, uploadURL,
           convertedTarredSegmentFile);
       if (!FileUtils.deleteQuietly(convertedTarredSegmentFile)) {
@@ -176,4 +189,10 @@ public abstract class BaseSingleSegmentConversionExecutor extends BaseTaskExecut
       FileUtils.deleteQuietly(tempDataDir);
     }
   }
+
+  // For tests only.
+  @VisibleForTesting
+  public void setMinionEventObserver(MinionEventObserver observer) {
+    _eventObserver = observer;
+  }
 }
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/converttorawindex/ConvertToRawIndexTaskExecutor.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/converttorawindex/ConvertToRawIndexTaskExecutor.java
index 53cc61068f..21ea6467c7 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/converttorawindex/ConvertToRawIndexTaskExecutor.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/converttorawindex/ConvertToRawIndexTaskExecutor.java
@@ -39,6 +39,7 @@ public class ConvertToRawIndexTaskExecutor extends BaseSingleSegmentConversionEx
     Map<String, String> configs = pinotTaskConfig.getConfigs();
     String tableNameWithType = configs.get(MinionConstants.TABLE_NAME_KEY);
     String rawTableName = TableNameBuilder.extractRawTableName(tableNameWithType);
+    _eventObserver.notifyProgress(pinotTaskConfig, "Converting segment: " + indexDir);
     new RawIndexConverter(rawTableName, indexDir, workingDir,
         configs.get(MinionConstants.ConvertToRawIndexTask.COLUMNS_TO_CONVERT_KEY),
         IndexingOverrides.getIndexCreatorProvider()).convert();
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/converttorawindex/ConvertToRowIndexTaskProgressObserverFactory.java
similarity index 59%
copy from pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java
copy to pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/converttorawindex/ConvertToRowIndexTaskProgressObserverFactory.java
index 6edd11bf79..53d456d9f6 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/converttorawindex/ConvertToRowIndexTaskProgressObserverFactory.java
@@ -16,30 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.pinot.plugin.minion.tasks.segmentgenerationandpush;
+package org.apache.pinot.plugin.minion.tasks.converttorawindex;
 
 import org.apache.pinot.core.common.MinionConstants;
-import org.apache.pinot.minion.event.MinionEventObserver;
-import org.apache.pinot.minion.event.MinionEventObserverFactory;
-import org.apache.pinot.minion.event.MinionProgressObserver;
-import org.apache.pinot.minion.executor.MinionTaskZkMetadataManager;
+import org.apache.pinot.minion.event.BaseMinionProgressObserverFactory;
 import org.apache.pinot.spi.annotations.minion.EventObserverFactory;
 
 
 @EventObserverFactory
-public class SegmentGenerationAndPushTaskProgressObserverFactory implements MinionEventObserverFactory {
-
-  @Override
-  public void init(MinionTaskZkMetadataManager zkMetadataManager) {
-  }
+public class ConvertToRowIndexTaskProgressObserverFactory extends BaseMinionProgressObserverFactory {
 
   @Override
   public String getTaskType() {
-    return MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE;
-  }
-
-  @Override
-  public MinionEventObserver create() {
-    return new MinionProgressObserver();
+    return MinionConstants.ConvertToRawIndexTask.TASK_TYPE;
   }
 }
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/mergerollup/MergeRollupTaskExecutor.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/mergerollup/MergeRollupTaskExecutor.java
index f054b024d4..36b5eddb60 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/mergerollup/MergeRollupTaskExecutor.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/mergerollup/MergeRollupTaskExecutor.java
@@ -55,6 +55,8 @@ public class MergeRollupTaskExecutor extends BaseMultipleSegmentsConversionExecu
   protected List<SegmentConversionResult> convert(PinotTaskConfig pinotTaskConfig, List<File> segmentDirs,
       File workingDir)
       throws Exception {
+    int numInputSegments = segmentDirs.size();
+    _eventObserver.notifyProgress(pinotTaskConfig, "Converting segments: " + numInputSegments);
     String taskType = pinotTaskConfig.getTaskType();
     Map<String, String> configs = pinotTaskConfig.getConfigs();
     LOGGER.info("Starting task: {} with configs: {}", taskType, configs);
@@ -86,8 +88,11 @@ public class MergeRollupTaskExecutor extends BaseMultipleSegmentsConversionExecu
 
     SegmentProcessorConfig segmentProcessorConfig = segmentProcessorConfigBuilder.build();
 
-    List<RecordReader> recordReaders = new ArrayList<>(segmentDirs.size());
+    List<RecordReader> recordReaders = new ArrayList<>(numInputSegments);
+    int count = 1;
     for (File segmentDir : segmentDirs) {
+      _eventObserver.notifyProgress(_pinotTaskConfig,
+          String.format("Creating RecordReader for: %s, %d/%d", segmentDir, count++, numInputSegments));
       PinotSegmentRecordReader recordReader = new PinotSegmentRecordReader();
       // NOTE: Do not fill null field with default value to be consistent with other record readers
       recordReader.init(segmentDir, null, null, true);
@@ -95,6 +100,7 @@ public class MergeRollupTaskExecutor extends BaseMultipleSegmentsConversionExecu
     }
     List<File> outputSegmentDirs;
     try {
+      _eventObserver.notifyProgress(_pinotTaskConfig, "Generating segments");
       outputSegmentDirs = new SegmentProcessorFramework(recordReaders, segmentProcessorConfig, workingDir).process();
     } finally {
       for (RecordReader recordReader : recordReaders) {
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/mergerollup/MergeRollupTaskProgressObserverFactory.java
similarity index 59%
copy from pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java
copy to pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/mergerollup/MergeRollupTaskProgressObserverFactory.java
index 6edd11bf79..451aee6b14 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/mergerollup/MergeRollupTaskProgressObserverFactory.java
@@ -16,30 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.pinot.plugin.minion.tasks.segmentgenerationandpush;
+package org.apache.pinot.plugin.minion.tasks.mergerollup;
 
 import org.apache.pinot.core.common.MinionConstants;
-import org.apache.pinot.minion.event.MinionEventObserver;
-import org.apache.pinot.minion.event.MinionEventObserverFactory;
-import org.apache.pinot.minion.event.MinionProgressObserver;
-import org.apache.pinot.minion.executor.MinionTaskZkMetadataManager;
+import org.apache.pinot.minion.event.BaseMinionProgressObserverFactory;
 import org.apache.pinot.spi.annotations.minion.EventObserverFactory;
 
 
 @EventObserverFactory
-public class SegmentGenerationAndPushTaskProgressObserverFactory implements MinionEventObserverFactory {
-
-  @Override
-  public void init(MinionTaskZkMetadataManager zkMetadataManager) {
-  }
+public class MergeRollupTaskProgressObserverFactory extends BaseMinionProgressObserverFactory {
 
   @Override
   public String getTaskType() {
-    return MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE;
-  }
-
-  @Override
-  public MinionEventObserver create() {
-    return new MinionProgressObserver();
+    return MinionConstants.MergeRollupTask.TASK_TYPE;
   }
 }
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/purge/PurgeTaskExecutor.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/purge/PurgeTaskExecutor.java
index f723163959..542bbde7da 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/purge/PurgeTaskExecutor.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/purge/PurgeTaskExecutor.java
@@ -52,6 +52,7 @@ public class PurgeTaskExecutor extends BaseSingleSegmentConversionExecutor {
     SegmentPurger.RecordModifier recordModifier =
         recordModifierFactory != null ? recordModifierFactory.getRecordModifier(rawTableName) : null;
 
+    _eventObserver.notifyProgress(pinotTaskConfig, "Purging segment: " + indexDir);
     SegmentPurger segmentPurger = new SegmentPurger(indexDir, workingDir, tableConfig, recordPurger, recordModifier);
     File purgedSegmentFile = segmentPurger.purgeSegment();
     if (purgedSegmentFile == null) {
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/purge/PurgeTaskProgressObserverFactory.java
similarity index 59%
copy from pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java
copy to pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/purge/PurgeTaskProgressObserverFactory.java
index 6edd11bf79..303b879310 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/purge/PurgeTaskProgressObserverFactory.java
@@ -16,30 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.pinot.plugin.minion.tasks.segmentgenerationandpush;
+package org.apache.pinot.plugin.minion.tasks.purge;
 
 import org.apache.pinot.core.common.MinionConstants;
-import org.apache.pinot.minion.event.MinionEventObserver;
-import org.apache.pinot.minion.event.MinionEventObserverFactory;
-import org.apache.pinot.minion.event.MinionProgressObserver;
-import org.apache.pinot.minion.executor.MinionTaskZkMetadataManager;
+import org.apache.pinot.minion.event.BaseMinionProgressObserverFactory;
 import org.apache.pinot.spi.annotations.minion.EventObserverFactory;
 
 
 @EventObserverFactory
-public class SegmentGenerationAndPushTaskProgressObserverFactory implements MinionEventObserverFactory {
-
-  @Override
-  public void init(MinionTaskZkMetadataManager zkMetadataManager) {
-  }
+public class PurgeTaskProgressObserverFactory extends BaseMinionProgressObserverFactory {
 
   @Override
   public String getTaskType() {
-    return MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE;
-  }
-
-  @Override
-  public MinionEventObserver create() {
-    return new MinionProgressObserver();
+    return MinionConstants.PurgeTask.TASK_TYPE;
   }
 }
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/realtimetoofflinesegments/RealtimeToOfflineSegmentsTaskExecutor.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/realtimetoofflinesegments/RealtimeToOfflineSegmentsTaskExecutor.java
index 88c011cbb8..fffb4b7bce 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/realtimetoofflinesegments/RealtimeToOfflineSegmentsTaskExecutor.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/realtimetoofflinesegments/RealtimeToOfflineSegmentsTaskExecutor.java
@@ -111,6 +111,8 @@ public class RealtimeToOfflineSegmentsTaskExecutor extends BaseMultipleSegmentsC
   protected List<SegmentConversionResult> convert(PinotTaskConfig pinotTaskConfig, List<File> segmentDirs,
       File workingDir)
       throws Exception {
+    int numInputSegments = segmentDirs.size();
+    _eventObserver.notifyProgress(pinotTaskConfig, "Converting segments: " + numInputSegments);
     String taskType = pinotTaskConfig.getTaskType();
     Map<String, String> configs = pinotTaskConfig.getConfigs();
     LOGGER.info("Starting task: {} with configs: {}", taskType, configs);
@@ -152,8 +154,11 @@ public class RealtimeToOfflineSegmentsTaskExecutor extends BaseMultipleSegmentsC
 
     SegmentProcessorConfig segmentProcessorConfig = segmentProcessorConfigBuilder.build();
 
-    List<RecordReader> recordReaders = new ArrayList<>(segmentDirs.size());
+    List<RecordReader> recordReaders = new ArrayList<>(numInputSegments);
+    int count = 1;
     for (File segmentDir : segmentDirs) {
+      _eventObserver.notifyProgress(_pinotTaskConfig,
+          String.format("Creating RecordReader for: %s, %d/%d", segmentDir, count++, numInputSegments));
       PinotSegmentRecordReader recordReader = new PinotSegmentRecordReader();
       // NOTE: Do not fill null field with default value to be consistent with other record readers
       recordReader.init(segmentDir, null, null, true);
@@ -161,6 +166,7 @@ public class RealtimeToOfflineSegmentsTaskExecutor extends BaseMultipleSegmentsC
     }
     List<File> outputSegmentDirs;
     try {
+      _eventObserver.notifyProgress(_pinotTaskConfig, "Generating segments");
       outputSegmentDirs = new SegmentProcessorFramework(recordReaders, segmentProcessorConfig, workingDir).process();
     } finally {
       for (RecordReader recordReader : recordReaders) {
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/realtimetoofflinesegments/RealtimeToOfflineSegmentsTaskProgressObserverFactory.java
similarity index 59%
copy from pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java
copy to pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/realtimetoofflinesegments/RealtimeToOfflineSegmentsTaskProgressObserverFactory.java
index 6edd11bf79..6d93f4be64 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/realtimetoofflinesegments/RealtimeToOfflineSegmentsTaskProgressObserverFactory.java
@@ -16,30 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.pinot.plugin.minion.tasks.segmentgenerationandpush;
+package org.apache.pinot.plugin.minion.tasks.realtimetoofflinesegments;
 
 import org.apache.pinot.core.common.MinionConstants;
-import org.apache.pinot.minion.event.MinionEventObserver;
-import org.apache.pinot.minion.event.MinionEventObserverFactory;
-import org.apache.pinot.minion.event.MinionProgressObserver;
-import org.apache.pinot.minion.executor.MinionTaskZkMetadataManager;
+import org.apache.pinot.minion.event.BaseMinionProgressObserverFactory;
 import org.apache.pinot.spi.annotations.minion.EventObserverFactory;
 
 
 @EventObserverFactory
-public class SegmentGenerationAndPushTaskProgressObserverFactory implements MinionEventObserverFactory {
-
-  @Override
-  public void init(MinionTaskZkMetadataManager zkMetadataManager) {
-  }
+public class RealtimeToOfflineSegmentsTaskProgressObserverFactory extends BaseMinionProgressObserverFactory {
 
   @Override
   public String getTaskType() {
-    return MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE;
-  }
-
-  @Override
-  public MinionEventObserver create() {
-    return new MinionProgressObserver();
+    return MinionConstants.RealtimeToOfflineSegmentsTask.TASK_TYPE;
   }
 }
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskExecutor.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskExecutor.java
index 76dd23dc3b..a17bbc77f4 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskExecutor.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskExecutor.java
@@ -120,7 +120,6 @@ public class SegmentGenerationAndPushTaskExecutor extends BaseTaskExecutor {
         "tmp-" + UUID.randomUUID());
     _pinotTaskConfig = pinotTaskConfig;
     _eventObserver = MinionEventObservers.getInstance().getMinionEventObserver(pinotTaskConfig.getTaskId());
-    _eventObserver.notifyProgress(pinotTaskConfig, "Task running");
     try {
       SegmentGenerationTaskSpec taskSpec = generateTaskSpec(taskConfigs, localTempDir);
       return generateAndPushSegment(taskSpec, resultBuilder, taskConfigs);
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java
index 6edd11bf79..da979be495 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/segmentgenerationandpush/SegmentGenerationAndPushTaskProgressObserverFactory.java
@@ -19,27 +19,15 @@
 package org.apache.pinot.plugin.minion.tasks.segmentgenerationandpush;
 
 import org.apache.pinot.core.common.MinionConstants;
-import org.apache.pinot.minion.event.MinionEventObserver;
-import org.apache.pinot.minion.event.MinionEventObserverFactory;
-import org.apache.pinot.minion.event.MinionProgressObserver;
-import org.apache.pinot.minion.executor.MinionTaskZkMetadataManager;
+import org.apache.pinot.minion.event.BaseMinionProgressObserverFactory;
 import org.apache.pinot.spi.annotations.minion.EventObserverFactory;
 
 
 @EventObserverFactory
-public class SegmentGenerationAndPushTaskProgressObserverFactory implements MinionEventObserverFactory {
-
-  @Override
-  public void init(MinionTaskZkMetadataManager zkMetadataManager) {
-  }
+public class SegmentGenerationAndPushTaskProgressObserverFactory extends BaseMinionProgressObserverFactory {
 
   @Override
   public String getTaskType() {
     return MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE;
   }
-
-  @Override
-  public MinionEventObserver create() {
-    return new MinionProgressObserver();
-  }
 }
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/mergerollup/MergeRollupTaskExecutorTest.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/mergerollup/MergeRollupTaskExecutorTest.java
index 233323a61b..03d4244116 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/mergerollup/MergeRollupTaskExecutorTest.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/mergerollup/MergeRollupTaskExecutorTest.java
@@ -33,6 +33,7 @@ import org.apache.pinot.core.common.MinionConstants;
 import org.apache.pinot.core.minion.PinotTaskConfig;
 import org.apache.pinot.minion.MinionConf;
 import org.apache.pinot.minion.MinionContext;
+import org.apache.pinot.minion.event.MinionProgressObserver;
 import org.apache.pinot.plugin.minion.tasks.SegmentConversionResult;
 import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl;
 import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader;
@@ -106,6 +107,7 @@ public class MergeRollupTaskExecutorTest {
   public void testConvert()
       throws Exception {
     MergeRollupTaskExecutor mergeRollupTaskExecutor = new MergeRollupTaskExecutor(new MinionConf());
+    mergeRollupTaskExecutor.setMinionEventObserver(new MinionProgressObserver());
     Map<String, String> configs = new HashMap<>();
     configs.put(MinionConstants.TABLE_NAME_KEY, "testTable_OFFLINE");
     configs.put(MinionConstants.MergeRollupTask.MERGE_LEVEL_KEY, "daily");
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/purge/PurgeTaskExecutorTest.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/purge/PurgeTaskExecutorTest.java
index 8fa8917f3e..d8f955be8a 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/purge/PurgeTaskExecutorTest.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/purge/PurgeTaskExecutorTest.java
@@ -30,6 +30,7 @@ import org.apache.pinot.common.utils.config.TableConfigUtils;
 import org.apache.pinot.core.common.MinionConstants;
 import org.apache.pinot.core.minion.PinotTaskConfig;
 import org.apache.pinot.minion.MinionContext;
+import org.apache.pinot.minion.event.MinionProgressObserver;
 import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl;
 import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader;
 import org.apache.pinot.segment.local.segment.readers.PinotSegmentRecordReader;
@@ -118,6 +119,7 @@ public class PurgeTaskExecutorTest {
   public void testConvert()
       throws Exception {
     PurgeTaskExecutor purgeTaskExecutor = new PurgeTaskExecutor();
+    purgeTaskExecutor.setMinionEventObserver(new MinionProgressObserver());
     PinotTaskConfig pinotTaskConfig = new PinotTaskConfig(MinionConstants.PurgeTask.TASK_TYPE, Collections
         .singletonMap(MinionConstants.TABLE_NAME_KEY, TableNameBuilder.OFFLINE.tableNameWithType(TABLE_NAME)));
     File purgedIndexDir = purgeTaskExecutor.convert(pinotTaskConfig, _originalIndexDir, PURGED_SEGMENT_DIR).getFile();
diff --git a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/realtimetoofflinesegments/RealtimeToOfflineSegmentsTaskExecutorTest.java b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/realtimetoofflinesegments/RealtimeToOfflineSegmentsTaskExecutorTest.java
index 3fcd2edaf2..99388c1f08 100644
--- a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/realtimetoofflinesegments/RealtimeToOfflineSegmentsTaskExecutorTest.java
+++ b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/realtimetoofflinesegments/RealtimeToOfflineSegmentsTaskExecutorTest.java
@@ -34,6 +34,7 @@ import org.apache.pinot.common.utils.config.TableConfigUtils;
 import org.apache.pinot.core.common.MinionConstants;
 import org.apache.pinot.core.minion.PinotTaskConfig;
 import org.apache.pinot.minion.MinionContext;
+import org.apache.pinot.minion.event.MinionProgressObserver;
 import org.apache.pinot.plugin.minion.tasks.SegmentConversionResult;
 import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl;
 import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader;
@@ -220,6 +221,7 @@ public class RealtimeToOfflineSegmentsTaskExecutorTest {
 
     RealtimeToOfflineSegmentsTaskExecutor realtimeToOfflineSegmentsTaskExecutor =
         new RealtimeToOfflineSegmentsTaskExecutor(null, null);
+    realtimeToOfflineSegmentsTaskExecutor.setMinionEventObserver(new MinionProgressObserver());
     Map<String, String> configs = new HashMap<>();
     configs.put(MinionConstants.TABLE_NAME_KEY, "testTable_OFFLINE");
     configs.put(MinionConstants.RealtimeToOfflineSegmentsTask.WINDOW_START_MS_KEY, "1600473600000");
@@ -247,6 +249,7 @@ public class RealtimeToOfflineSegmentsTaskExecutorTest {
 
     RealtimeToOfflineSegmentsTaskExecutor realtimeToOfflineSegmentsTaskExecutor =
         new RealtimeToOfflineSegmentsTaskExecutor(null, null);
+    realtimeToOfflineSegmentsTaskExecutor.setMinionEventObserver(new MinionProgressObserver());
     Map<String, String> configs = new HashMap<>();
     configs.put(MinionConstants.TABLE_NAME_KEY, TABLE_NAME);
     configs.put(MinionConstants.RealtimeToOfflineSegmentsTask.WINDOW_START_MS_KEY, "1600473600000");
@@ -275,6 +278,7 @@ public class RealtimeToOfflineSegmentsTaskExecutorTest {
 
     RealtimeToOfflineSegmentsTaskExecutor realtimeToOfflineSegmentsTaskExecutor =
         new RealtimeToOfflineSegmentsTaskExecutor(null, null);
+    realtimeToOfflineSegmentsTaskExecutor.setMinionEventObserver(new MinionProgressObserver());
     Map<String, String> configs = new HashMap<>();
     configs.put(MinionConstants.TABLE_NAME_KEY, TABLE_NAME);
     configs.put(MinionConstants.RealtimeToOfflineSegmentsTask.WINDOW_START_MS_KEY, "1600473600000");
@@ -304,6 +308,7 @@ public class RealtimeToOfflineSegmentsTaskExecutorTest {
 
     RealtimeToOfflineSegmentsTaskExecutor realtimeToOfflineSegmentsTaskExecutor =
         new RealtimeToOfflineSegmentsTaskExecutor(null, null);
+    realtimeToOfflineSegmentsTaskExecutor.setMinionEventObserver(new MinionProgressObserver());
     Map<String, String> configs = new HashMap<>();
     configs.put(MinionConstants.TABLE_NAME_KEY, TABLE_NAME);
     configs.put(MinionConstants.RealtimeToOfflineSegmentsTask.WINDOW_START_MS_KEY, "1600473600000");
@@ -337,6 +342,7 @@ public class RealtimeToOfflineSegmentsTaskExecutorTest {
 
     RealtimeToOfflineSegmentsTaskExecutor realtimeToOfflineSegmentsTaskExecutor =
         new RealtimeToOfflineSegmentsTaskExecutor(null, null);
+    realtimeToOfflineSegmentsTaskExecutor.setMinionEventObserver(new MinionProgressObserver());
     Map<String, String> configs = new HashMap<>();
     configs.put(MinionConstants.TABLE_NAME_KEY, TABLE_NAME_WITH_PARTITIONING);
     configs.put(MinionConstants.RealtimeToOfflineSegmentsTask.WINDOW_START_MS_KEY, "1600468000000");
@@ -369,6 +375,7 @@ public class RealtimeToOfflineSegmentsTaskExecutorTest {
 
     RealtimeToOfflineSegmentsTaskExecutor realtimeToOfflineSegmentsTaskExecutor =
         new RealtimeToOfflineSegmentsTaskExecutor(null, null);
+    realtimeToOfflineSegmentsTaskExecutor.setMinionEventObserver(new MinionProgressObserver());
     Map<String, String> configs = new HashMap<>();
     configs.put(MinionConstants.TABLE_NAME_KEY, TABLE_NAME_WITH_SORTED_COL);
     configs.put(MinionConstants.RealtimeToOfflineSegmentsTask.WINDOW_START_MS_KEY, "1600473600000");
@@ -397,6 +404,7 @@ public class RealtimeToOfflineSegmentsTaskExecutorTest {
 
     RealtimeToOfflineSegmentsTaskExecutor realtimeToOfflineSegmentsTaskExecutor =
         new RealtimeToOfflineSegmentsTaskExecutor(null, null);
+    realtimeToOfflineSegmentsTaskExecutor.setMinionEventObserver(new MinionProgressObserver());
     Map<String, String> configs = new HashMap<>();
     configs.put(MinionConstants.TABLE_NAME_KEY, TABLE_NAME_EPOCH_HOURS);
     configs.put(MinionConstants.RealtimeToOfflineSegmentsTask.WINDOW_START_MS_KEY, "1600473600000");
@@ -426,6 +434,7 @@ public class RealtimeToOfflineSegmentsTaskExecutorTest {
 
     RealtimeToOfflineSegmentsTaskExecutor realtimeToOfflineSegmentsTaskExecutor =
         new RealtimeToOfflineSegmentsTaskExecutor(null, null);
+    realtimeToOfflineSegmentsTaskExecutor.setMinionEventObserver(new MinionProgressObserver());
     Map<String, String> configs = new HashMap<>();
     configs.put(MinionConstants.TABLE_NAME_KEY, TABLE_NAME_SDF);
     configs.put(MinionConstants.RealtimeToOfflineSegmentsTask.WINDOW_START_MS_KEY, "1600473600000");


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org