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

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 0f7211bede Some improvements to the metrics panel, fixes #6599 (#6600)
0f7211bede is described below

commit 0f7211bede20066fb027608bad11a9e8db5c78fc
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Tue Feb 17 20:14:39 2026 +0100

    Some improvements to the metrics panel, fixes #6599 (#6600)
---
 .../main/java/org/apache/hop/ui/core/PropsUi.java  |  66 +++++
 .../org/apache/hop/ui/core/widget/ColumnInfo.java  | 210 ++------------
 .../file/pipeline/PipelineMetricDisplayUtil.java   | 105 +++++++
 .../delegates/HopGuiPipelineGridDelegate.java      | 306 ++++++++++++++++++++-
 .../configuration/tabs/ConfigGuiOptionsTab.java    | 146 ++++++++++
 .../execution/PipelineExecutionViewer.java         |  10 +-
 .../core/dialog/messages/messages_en_US.properties |   9 +
 .../ui/hopgui/messages/messages_en_US.properties   |  33 ++-
 8 files changed, 678 insertions(+), 207 deletions(-)

diff --git a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java 
b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
index bd6705fcea..68eba5018f 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
@@ -88,6 +88,15 @@ public class PropsUi extends Props {
   private static final String USE_ADVANCED_TERMINAL = "UseAdvancedTerminal";
   private static final String RESET_DIALOG_POSITIONS_ON_RESTART = 
"ResetDialogPositionsOnRestart";
 
+  // Metrics panel (pipeline execution grid) – "Show" options
+  private static final String METRICS_PANEL_SHOW_UNITS = 
"MetricsPanel.ShowUnits";
+  private static final String METRICS_PANEL_SHOW_INPUT = 
"MetricsPanel.ShowInput";
+  private static final String METRICS_PANEL_SHOW_READ = 
"MetricsPanel.ShowRead";
+  private static final String METRICS_PANEL_SHOW_OUTPUT = 
"MetricsPanel.ShowOutput";
+  private static final String METRICS_PANEL_SHOW_UPDATED = 
"MetricsPanel.ShowUpdated";
+  private static final String METRICS_PANEL_SHOW_REJECTED = 
"MetricsPanel.ShowRejected";
+  private static final String METRICS_PANEL_SHOW_BUFFERS_INPUT = 
"MetricsPanel.ShowBuffersInput";
+
   public static final int DEFAULT_MAX_EXECUTION_LOGGING_TEXT_SIZE = 2000000;
   private Map<RGB, RGB> contrastingColors;
   private static PropsUi instance;
@@ -586,6 +595,63 @@ public class PropsUi extends Props {
     setProperty(STRING_SHOW_TABLE_VIEW_TOOLBAR, show ? YES : NO);
   }
 
+  /** Show units in grid cells (default false). */
+  public boolean isMetricsPanelShowUnits() {
+    return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_UNITS, NO));
+  }
+
+  public void setMetricsPanelShowUnits(boolean show) {
+    setProperty(METRICS_PANEL_SHOW_UNITS, show ? YES : NO);
+  }
+
+  public boolean isMetricsPanelShowInput() {
+    return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_INPUT, YES));
+  }
+
+  public void setMetricsPanelShowInput(boolean show) {
+    setProperty(METRICS_PANEL_SHOW_INPUT, show ? YES : NO);
+  }
+
+  public boolean isMetricsPanelShowRead() {
+    return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_READ, YES));
+  }
+
+  public void setMetricsPanelShowRead(boolean show) {
+    setProperty(METRICS_PANEL_SHOW_READ, show ? YES : NO);
+  }
+
+  public boolean isMetricsPanelShowOutput() {
+    return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_OUTPUT, YES));
+  }
+
+  public void setMetricsPanelShowOutput(boolean show) {
+    setProperty(METRICS_PANEL_SHOW_OUTPUT, show ? YES : NO);
+  }
+
+  public boolean isMetricsPanelShowUpdated() {
+    return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_UPDATED, YES));
+  }
+
+  public void setMetricsPanelShowUpdated(boolean show) {
+    setProperty(METRICS_PANEL_SHOW_UPDATED, show ? YES : NO);
+  }
+
+  public boolean isMetricsPanelShowRejected() {
+    return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_REJECTED, YES));
+  }
+
+  public void setMetricsPanelShowRejected(boolean show) {
+    setProperty(METRICS_PANEL_SHOW_REJECTED, show ? YES : NO);
+  }
+
+  public boolean isMetricsPanelShowBuffersInput() {
+    return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_BUFFERS_INPUT, 
YES));
+  }
+
+  public void setMetricsPanelShowBuffersInput(boolean show) {
+    setProperty(METRICS_PANEL_SHOW_BUFFERS_INPUT, show ? YES : NO);
+  }
+
   public static void setLook(Widget widget) {
     int style = WIDGET_STYLE_DEFAULT;
     if (widget instanceof Table) {
diff --git a/ui/src/main/java/org/apache/hop/ui/core/widget/ColumnInfo.java 
b/ui/src/main/java/org/apache/hop/ui/core/widget/ColumnInfo.java
index 6d15f84489..9c75e86935 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/widget/ColumnInfo.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/widget/ColumnInfo.java
@@ -18,6 +18,8 @@
 package org.apache.hop.ui.core.widget;
 
 import java.util.function.Supplier;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.hop.core.row.IValueMeta;
 import org.apache.hop.core.row.value.ValueMetaInteger;
 import org.apache.hop.core.row.value.ValueMetaString;
@@ -35,35 +37,39 @@ public class ColumnInfo {
   public static final int COLUMN_TYPE_FORMAT = 5;
   public static final int COLUMN_TYPE_TEXT_BUTTON = 6;
 
-  private int type;
-  private String name;
+  @Getter private final int type;
 
-  private String[] comboValues;
-  private Supplier<String[]> comboValueSupplier = () -> comboValues;
-  private boolean numeric;
-  private String tooltip;
-  private Image image;
-  private int alignment;
+  @Getter private final String name;
+
+  @Setter private String[] comboValues;
+  @Setter @Getter private Supplier<String[]> comboValueSupplier = () -> 
comboValues;
+  @Getter @Setter private boolean numeric;
+  @Getter @Setter private String tooltip;
+  @Getter @Setter private Image image;
+  @Setter @Getter private int alignment;
   private boolean readonly;
-  private String buttonText;
+  @Setter @Getter private String buttonText;
   private boolean hidingNegativeValues;
-  private int width = -1;
-  private boolean autoResize = true;
+  @Getter @Setter private int width = -1;
+
+  @Getter @Setter private boolean autoResize = true;
 
-  private IValueMeta valueMeta;
+  @Getter @Setter private IValueMeta valueMeta;
 
   private SelectionListener selButton;
-  private SelectionListener textVarButtonSelectionListener;
 
-  private ITextVarButtonRenderCallback renderTextVarButtonCallback;
+  @Getter @Setter private SelectionListener textVarButtonSelectionListener;
+
+  @Getter @Setter private ITextVarButtonRenderCallback 
renderTextVarButtonCallback;
 
-  private IFieldDisabledListener disabledListener;
+  @Getter @Setter private IFieldDisabledListener disabledListener;
 
-  private boolean usingVariables;
-  private boolean passwordField;
+  @Getter @Setter private boolean usingVariables;
 
-  private IComboValuesSelectionListener comboValuesSelectionListener;
-  private int fieldTypeColumn;
+  @Getter @Setter private boolean passwordField;
+
+  @Getter @Setter private IComboValuesSelectionListener 
comboValuesSelectionListener;
+  @Getter @Setter private int fieldTypeColumn;
 
   /**
    * Creates a column info class for use with the TableView class.
@@ -197,77 +203,18 @@ public class ColumnInfo {
     readonly = ro;
   }
 
-  public void setAlignment(int allign) {
-    alignment = allign;
-  }
-
-  public void setComboValues(String[] cv) {
-    comboValues = cv;
-  }
-
-  public void setComboValueSupplier(Supplier<String[]> comboValueSupplier) {
-    this.comboValueSupplier = comboValueSupplier;
-  }
-
-  public void setButtonText(String bt) {
-    buttonText = bt;
-  }
-
-  public String getName() {
-    return name;
-  }
-
-  public int getType() {
-    return type;
-  }
-
   public String[] getComboValues() {
     return comboValueSupplier.get();
   }
 
-  /**
-   * @return the numeric
-   */
-  public boolean isNumeric() {
-    return numeric;
-  }
-
-  /**
-   * @param numeric the numeric to set
-   */
-  public void setNumeric(boolean numeric) {
-    this.numeric = numeric;
-  }
-
   public String getToolTip() {
     return tooltip;
   }
 
-  public Image getImage() {
-    return image;
-  }
-
-  /**
-   * Sets the column's image to be displayed.
-   *
-   * @param image the image to display on the receiver (may be null)
-   */
-  public void setImage(Image image) {
-    this.image = image;
-  }
-
-  public int getAlignment() {
-    return alignment;
-  }
-
   public boolean isReadOnly() {
     return readonly;
   }
 
-  public String getButtonText() {
-    return buttonText;
-  }
-
   public void setSelectionAdapter(SelectionListener sb) {
     selButton = sb;
   }
@@ -288,115 +235,8 @@ public class ColumnInfo {
     return hidingNegativeValues;
   }
 
-  /**
-   * @return the valueMeta
-   */
-  public IValueMeta getValueMeta() {
-    return valueMeta;
-  }
-
-  /**
-   * @param valueMeta the valueMeta to set
-   */
-  public void setValueMeta(IValueMeta valueMeta) {
-    this.valueMeta = valueMeta;
-  }
-
-  /**
-   * @return the usingVariables
-   */
-  public boolean isUsingVariables() {
-    return usingVariables;
-  }
-
-  /**
-   * @param usingVariables the usingVariables to set
-   */
-  public void setUsingVariables(boolean usingVariables) {
-    this.usingVariables = usingVariables;
-  }
-
-  /**
-   * @return the password
-   */
-  public boolean isPasswordField() {
-    return passwordField;
-  }
-
-  /**
-   * @param password the password to set
-   */
-  public void setPasswordField(boolean password) {
-    this.passwordField = password;
-  }
-
-  public int getFieldTypeColumn() {
-    return fieldTypeColumn;
-  }
-
-  public void setFieldTypeColumn(int fieldTypeColumn) {
-    this.fieldTypeColumn = fieldTypeColumn;
-  }
-
-  /**
-   * @return the comboValuesSelectionListener
-   */
-  public IComboValuesSelectionListener getComboValuesSelectionListener() {
-    return comboValuesSelectionListener;
-  }
-
-  /**
-   * @param comboValuesSelectionListener the comboValuesSelectionListener to 
set
-   */
-  public void setComboValuesSelectionListener(
-      IComboValuesSelectionListener comboValuesSelectionListener) {
-    this.comboValuesSelectionListener = comboValuesSelectionListener;
-  }
-
-  /**
-   * @return the disabledListener
-   */
-  public IFieldDisabledListener getDisabledListener() {
-    return disabledListener;
-  }
-
-  /**
-   * @param disabledListener the disabledListener to set
-   */
-  public void setDisabledListener(IFieldDisabledListener disabledListener) {
-    this.disabledListener = disabledListener;
-  }
-
-  public SelectionListener getTextVarButtonSelectionListener() {
-    return textVarButtonSelectionListener;
-  }
-
-  public void setTextVarButtonSelectionListener(SelectionListener 
textVarButtonSelectionListener) {
-    this.textVarButtonSelectionListener = textVarButtonSelectionListener;
-  }
-
-  public void setRenderTextVarButtonCallback(ITextVarButtonRenderCallback 
callback) {
-    this.renderTextVarButtonCallback = callback;
-  }
-
   public boolean shouldRenderTextVarButton() {
     return this.renderTextVarButtonCallback == null
         || this.renderTextVarButtonCallback.shouldRenderButton();
   }
-
-  public int getWidth() {
-    return this.width;
-  }
-
-  /**
-   * @return if should be resized to accommodate contents
-   */
-  public boolean isAutoResize() {
-    return autoResize;
-  }
-
-  /** If should be resized to accommodate contents. Default is 
<code>true</code>. */
-  public void setAutoResize(boolean resize) {
-    this.autoResize = resize;
-  }
 }
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/PipelineMetricDisplayUtil.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/PipelineMetricDisplayUtil.java
new file mode 100644
index 0000000000..e2d2c238c1
--- /dev/null
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/PipelineMetricDisplayUtil.java
@@ -0,0 +1,105 @@
+/*
+ * 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.hop.ui.hopgui.file.pipeline;
+
+import org.apache.hop.pipeline.Pipeline;
+import org.apache.hop.pipeline.engine.IEngineMetric;
+
+/**
+ * Utility for displaying pipeline metrics in the UI with appropriate units. 
Does not change metric
+ * storage or keys; only used for column headers and labels.
+ */
+public final class PipelineMetricDisplayUtil {
+
+  private PipelineMetricDisplayUtil() {}
+
+  /**
+   * Returns the metric header for display in the metrics tab/grid. The 
underlying metric key (e.g.
+   * for lookup) remains the raw header from {@link IEngineMetric#getHeader()}.
+   *
+   * @param metric the engine metric
+   * @param withUnit when true, append unit in parentheses (e.g. "Input 
(rows)"); when false, header
+   *     only (e.g. "Input")
+   * @return display label
+   */
+  public static String getDisplayHeader(IEngineMetric metric, boolean 
withUnit) {
+    if (metric == null) {
+      return "";
+    }
+    if (!withUnit) {
+      return metric.getHeader();
+    }
+    String unit = getUnitForMetric(metric);
+    if (unit == null || unit.isEmpty()) {
+      return metric.getHeader();
+    }
+    return metric.getHeader() + " (" + unit + ")";
+  }
+
+  /**
+   * Returns the metric header with the correct unit for display (same as 
getDisplayHeader(metric,
+   * true)).
+   */
+  public static String getDisplayHeaderWithUnit(IEngineMetric metric) {
+    return getDisplayHeader(metric, true);
+  }
+
+  /**
+   * Returns the unit label for a metric (e.g. "rows", "runs") for use in 
column headers. Returns
+   * null if the metric has no unit.
+   */
+  public static String getUnitForMetric(IEngineMetric metric) {
+    String code = metric.getCode();
+    if (code == null) {
+      return null;
+    }
+    switch (code) {
+      case Pipeline.METRIC_NAME_INPUT,
+          Pipeline.METRIC_NAME_READ,
+          Pipeline.METRIC_NAME_WRITTEN,
+          Pipeline.METRIC_NAME_OUTPUT,
+          Pipeline.METRIC_NAME_UPDATED,
+          Pipeline.METRIC_NAME_REJECTED,
+          Pipeline.METRIC_NAME_ERROR,
+          Pipeline.METRIC_NAME_BUFFER_IN,
+          Pipeline.METRIC_NAME_BUFFER_OUT:
+        return "rows";
+      case Pipeline.METRIC_NAME_INIT:
+        return "runs";
+      case Pipeline.METRIC_NAME_FLUSH_BUFFER:
+        return "flushes";
+      default:
+        return null;
+    }
+  }
+
+  /**
+   * Returns the short unit label for a metric for use in grid cell values 
(e.g. "r" for rows so the
+   * header can keep "rows"). Returns null if the metric has no unit.
+   */
+  public static String getUnitForMetricCell(IEngineMetric metric) {
+    String unit = getUnitForMetric(metric);
+    if (unit == null) {
+      return null;
+    }
+    if ("rows".equals(unit)) {
+      return "r";
+    }
+    return unit;
+  }
+}
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineGridDelegate.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineGridDelegate.java
index 6cae360d41..988f92d402 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineGridDelegate.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineGridDelegate.java
@@ -22,15 +22,19 @@ import java.text.NumberFormat;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Consumer;
 import lombok.Getter;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.Props;
+import org.apache.hop.core.config.HopConfig;
 import org.apache.hop.core.gui.plugin.GuiPlugin;
 import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElement;
 import org.apache.hop.core.row.IValueMeta;
@@ -57,16 +61,22 @@ import org.apache.hop.ui.hopgui.HopGui;
 import org.apache.hop.ui.hopgui.ToolbarFacade;
 import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
 import org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
+import org.apache.hop.ui.hopgui.file.pipeline.PipelineMetricDisplayUtil;
 import org.apache.hop.ui.hopgui.selection.HopGuiSelectionTracker;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.CTabItem;
 import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.layout.FormAttachment;
 import org.eclipse.swt.layout.FormData;
 import org.eclipse.swt.layout.FormLayout;
 import org.eclipse.swt.layout.RowData;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
 import org.eclipse.swt.widgets.Table;
 import org.eclipse.swt.widgets.TableColumn;
 import org.eclipse.swt.widgets.TableItem;
@@ -133,6 +143,24 @@ public class HopGuiPipelineGridDelegate {
 
   private Text searchText;
 
+  /**
+   * Keys for current table columns (e.g. "#", "TransformName", "input") used 
to save/restore column
+   * widths. Set when the table is created so we can save widths before 
dispose.
+   */
+  private List<String> metricsColumnKeys = null;
+
+  /**
+   * Column widths for the metrics grid (session-only, not persisted). Key = 
column key e.g. "#",
+   * "TransformName", "input".
+   */
+  private final Map<String, Integer> metricsColumnWidths = new HashMap<>();
+
+  /**
+   * True while we are programmatically restoring column widths. Resize 
listeners must not persist
+   * those changes or they overwrite the user's saved widths.
+   */
+  private boolean restoringColumnWidths = false;
+
   /**
    * @param hopGui Hop GUI instance
    * @param pipelineGraph the pipeline graph that owns this delegate
@@ -341,6 +369,7 @@ public class HopGuiPipelineGridDelegate {
                       || pipelineGraph.getPipeline().isStopped())
                   && !pipelineGraph.getPipeline().isReadyToStart()) {
                 ExecutorUtil.cleanup(refreshMetricsTimer, UPDATE_TIME_VIEW + 
10);
+                refreshMetricsTimer = null;
               }
             }
           }
@@ -385,11 +414,110 @@ public class HopGuiPipelineGridDelegate {
     toolbarWidget.registerGuiPluginObject(this);
     toolbarWidget.createToolbarWidgets(toolBarContainer, 
GUI_PLUGIN_TOOLBAR_PARENT_ID);
 
+    addMetricsViewDropdown(toolbar);
     addSearchBarToToolbar(toolbar);
 
     toolbar.pack();
   }
 
+  /**
+   * Add a "View" dropdown to the toolbar with checkable items for metrics 
panel options (hide
+   * units, hide columns). Syncs with PropsUi so options dialog and toolbar 
stay in sync.
+   */
+  private void addMetricsViewDropdown(Control toolbarControl) {
+    if (!(toolbarControl instanceof ToolBar tb)) {
+      return;
+    }
+    ToolItem viewItem = new ToolItem(tb, SWT.DROP_DOWN);
+    viewItem.setText(BaseMessages.getString(PKG, 
"PipelineLog.MetricsView.View"));
+    viewItem.setToolTipText(BaseMessages.getString(PKG, 
"PipelineLog.MetricsView.View.Tooltip"));
+
+    viewItem.addListener(
+        SWT.Selection,
+        e -> {
+          Menu menu = createMetricsViewMenu(tb);
+          Point point;
+          if (e.detail == SWT.ARROW) {
+            point = tb.toDisplay(e.x, e.y + viewItem.getBounds().height);
+          } else {
+            // Clicked the "View" label: show menu below the button
+            Rectangle bounds = viewItem.getBounds();
+            point = tb.toDisplay(bounds.x, bounds.y + bounds.height);
+          }
+          menu.setLocation(point.x, point.y);
+          menu.setVisible(true);
+        });
+  }
+
+  private Menu createMetricsViewMenu(ToolBar parent) {
+    Menu menu = new Menu(parent.getShell(), SWT.POP_UP);
+    PropsUi props = PropsUi.getInstance();
+
+    MenuItem showUnitsItem = new MenuItem(menu, SWT.CHECK);
+    showUnitsItem.setText(BaseMessages.getString(PKG, 
"PipelineLog.MetricsView.ShowUnits"));
+    showUnitsItem.setSelection(props.isMetricsPanelShowUnits());
+    showUnitsItem.addListener(
+        SWT.Selection,
+        e ->
+            setMetricsOptionAndRefresh(
+                () -> 
props.setMetricsPanelShowUnits(showUnitsItem.getSelection())));
+
+    new MenuItem(menu, SWT.SEPARATOR);
+
+    addMetricsViewMenuItem(
+        menu,
+        "PipelineLog.MetricsView.ShowInput",
+        props.isMetricsPanelShowInput(),
+        props::setMetricsPanelShowInput);
+    addMetricsViewMenuItem(
+        menu,
+        "PipelineLog.MetricsView.ShowRead",
+        props.isMetricsPanelShowRead(),
+        props::setMetricsPanelShowRead);
+    addMetricsViewMenuItem(
+        menu,
+        "PipelineLog.MetricsView.ShowOutput",
+        props.isMetricsPanelShowOutput(),
+        props::setMetricsPanelShowOutput);
+    addMetricsViewMenuItem(
+        menu,
+        "PipelineLog.MetricsView.ShowUpdated",
+        props.isMetricsPanelShowUpdated(),
+        props::setMetricsPanelShowUpdated);
+    addMetricsViewMenuItem(
+        menu,
+        "PipelineLog.MetricsView.ShowRejected",
+        props.isMetricsPanelShowRejected(),
+        props::setMetricsPanelShowRejected);
+    addMetricsViewMenuItem(
+        menu,
+        "PipelineLog.MetricsView.ShowBuffersInput",
+        props.isMetricsPanelShowBuffersInput(),
+        props::setMetricsPanelShowBuffersInput);
+
+    return menu;
+  }
+
+  private void addMetricsViewMenuItem(
+      Menu menu, String messageKey, boolean selected, Consumer<Boolean> 
setOption) {
+    MenuItem item = new MenuItem(menu, SWT.CHECK);
+    item.setText(BaseMessages.getString(PKG, messageKey));
+    item.setSelection(selected);
+    item.addListener(
+        SWT.Selection,
+        e -> setMetricsOptionAndRefresh(() -> 
setOption.accept(item.getSelection())));
+  }
+
+  private void setMetricsOptionAndRefresh(Runnable setOption) {
+    setOption.run();
+    try {
+      HopConfig.getInstance().saveToFile();
+    } catch (Exception ignored) {
+      // best-effort persist
+    }
+    refreshView();
+  }
+
   /**
    * Add the transform-name search bar to the toolbar. Layout differs for 
ToolBar (desktop) vs
    * Composite (web flow).
@@ -509,10 +637,11 @@ public class HopGuiPipelineGridDelegate {
           getShownComponents(engineMetrics, selectedTransformNames);
       List<IEngineMetric> usedMetrics = getUsedMetrics(engineMetrics);
       List<ColumnInfo> columns = buildColumnList(usedMetrics);
+      applySavedColumnWidthsToColumnInfos(columns, usedMetrics);
       List<List<String>> componentStringsList =
           buildComponentStringsList(engineMetrics, shownComponents, 
usedMetrics);
 
-      recreateTableIfColumnsChanged(columns, shownComponents.size());
+      recreateTableIfColumnsChanged(columns, shownComponents.size(), 
usedMetrics);
 
       sortComponentStringsByColumn(componentStringsList, gridSortColumn, 
gridSortDescending);
 
@@ -521,7 +650,11 @@ public class HopGuiPipelineGridDelegate {
 
       setSortIndicator();
 
+      // Ignore resize events from optWidth and restoreColumnWidths so we 
don't overwrite saved
+      // widths
+      restoringColumnWidths = true;
       pipelineGridView.optWidth(true);
+      restoreColumnWidths(usedMetrics);
       previousRefreshColumns = columns;
       updateEditButtonState();
     } finally {
@@ -583,11 +716,29 @@ public class HopGuiPipelineGridDelegate {
 
   private List<IEngineMetric> getUsedMetrics(EngineMetrics engineMetrics) {
     List<IEngineMetric> usedMetrics = new 
ArrayList<>(engineMetrics.getMetricsList());
+    usedMetrics.removeIf(this::isMetricHidden);
     Collections.sort(
         usedMetrics, (o1, o2) -> 
o1.getDisplayPriority().compareTo(o2.getDisplayPriority()));
     return usedMetrics;
   }
 
+  private boolean isMetricHidden(IEngineMetric metric) {
+    String code = metric.getCode();
+    if (code == null) {
+      return false;
+    }
+    PropsUi props = PropsUi.getInstance();
+    return switch (code) {
+      case Pipeline.METRIC_NAME_INPUT -> !props.isMetricsPanelShowInput();
+      case Pipeline.METRIC_NAME_READ -> !props.isMetricsPanelShowRead();
+      case Pipeline.METRIC_NAME_OUTPUT -> !props.isMetricsPanelShowOutput();
+      case Pipeline.METRIC_NAME_UPDATED -> !props.isMetricsPanelShowUpdated();
+      case Pipeline.METRIC_NAME_REJECTED -> 
!props.isMetricsPanelShowRejected();
+      case Pipeline.METRIC_NAME_BUFFER_IN -> 
!props.isMetricsPanelShowBuffersInput();
+      default -> false;
+    };
+  }
+
   private List<ColumnInfo> buildColumnList(List<IEngineMetric> usedMetrics) {
     List<ColumnInfo> columns = new ArrayList<>();
 
@@ -609,7 +760,11 @@ public class HopGuiPipelineGridDelegate {
 
     for (IEngineMetric metric : usedMetrics) {
       ColumnInfo column =
-          new ColumnInfo(metric.getHeader(), ColumnInfo.COLUMN_TYPE_TEXT, 
metric.isNumeric(), true);
+          new ColumnInfo(
+              PipelineMetricDisplayUtil.getDisplayHeaderWithUnit(metric),
+              ColumnInfo.COLUMN_TYPE_TEXT,
+              metric.isNumeric(),
+              true);
       column.setToolTip(metric.getTooltip());
       IValueMeta stringMeta = new ValueMetaString(metric.getCode());
       ValueMetaInteger valueMeta = new ValueMetaInteger(metric.getCode(), 15, 
0);
@@ -655,6 +810,22 @@ public class HopGuiPipelineGridDelegate {
     return columns;
   }
 
+  /**
+   * Applies saved column widths to ColumnInfo so that TableView.optWidth() 
will use them instead of
+   * auto-sizing. Table column 0 is the "#" column (no ColumnInfo); indices 
1..n match our columns.
+   */
+  private void applySavedColumnWidthsToColumnInfos(
+      List<ColumnInfo> columns, List<IEngineMetric> usedMetrics) {
+    List<String> keys = getColumnKeys(usedMetrics);
+    for (int i = 0; i < columns.size() && (i + 1) < keys.size(); i++) {
+      String key = keys.get(i + 1);
+      Integer w = metricsColumnWidths.get(key);
+      if (w != null && w > 0) {
+        columns.get(i).setWidth(w);
+      }
+    }
+  }
+
   private List<List<String>> buildComponentStringsList(
       EngineMetrics engineMetrics,
       List<IEngineComponent> shownComponents,
@@ -667,12 +838,26 @@ public class HopGuiPipelineGridDelegate {
       componentStrings.add(Const.NVL(component.getName(), ""));
       componentStrings.add(Integer.toString(component.getCopyNr()));
 
+      boolean showUnits = PropsUi.getInstance().isMetricsPanelShowUnits();
       for (IEngineMetric metric : usedMetrics) {
         Long value = engineMetrics.getComponentMetric(component, metric);
-        componentStrings.add(value == null ? "" : formatMetric(value));
+        String unit = showUnits ? 
PipelineMetricDisplayUtil.getUnitForMetricCell(metric) : null;
+        String cell =
+            value == null
+                ? ""
+                : formatMetric(value) + (unit != null && !unit.isEmpty() ? " " 
+ unit : "");
+        componentStrings.add(cell);
       }
-      componentStrings.add(calculateDuration(component));
-      
componentStrings.add(Const.NVL(engineMetrics.getComponentSpeedMap().get(component),
 ""));
+      String durationStr = calculateDuration(component);
+      componentStrings.add(
+          durationStr.isEmpty() ? "" : showUnits ? durationStr + " (h:m:s)" : 
durationStr);
+      String speedStr = engineMetrics.getComponentSpeedMap().get(component);
+      componentStrings.add(
+          speedStr == null || speedStr.isEmpty()
+              ? ""
+              : showUnits && !speedStr.trim().equals("-")
+                  ? speedStr.trim() + " rows/s"
+                  : speedStr.trim());
       
componentStrings.add(Const.NVL(engineMetrics.getComponentStatusMap().get(component),
 ""));
 
       componentStringsList.add(componentStrings);
@@ -680,10 +865,12 @@ public class HopGuiPipelineGridDelegate {
     return componentStringsList;
   }
 
-  private void recreateTableIfColumnsChanged(List<ColumnInfo> columns, int 
rowCount) {
+  private void recreateTableIfColumnsChanged(
+      List<ColumnInfo> columns, int rowCount, List<IEngineMetric> usedMetrics) 
{
     if (!haveColumnsChanged(columns)) {
       return;
     }
+    saveColumnWidths();
     pipelineGridView.dispose();
     pipelineGridView =
         new TableView(
@@ -700,6 +887,8 @@ public class HopGuiPipelineGridDelegate {
             false,
             false); // no TableView toolbar; copy/filter are on our toolbar
     pipelineGridView.setSortable(true);
+    metricsColumnKeys = getColumnKeys(usedMetrics);
+    attachColumnWidthListeners();
     attachMetricsTableListeners(pipelineGridView);
     FormData fdView = new FormData();
     fdView.left = new FormAttachment(0, 0);
@@ -708,6 +897,103 @@ public class HopGuiPipelineGridDelegate {
     fdView.bottom = new FormAttachment(100, 0);
     pipelineGridView.setLayoutData(fdView);
     pipelineGridComposite.layout(true, true);
+    restoreColumnWidths(usedMetrics);
+  }
+
+  /**
+   * Builds the list of column keys in table order (#, TransformName, Copy, 
metric codes, duration,
+   * speed, status).
+   */
+  private List<String> getColumnKeys(List<IEngineMetric> usedMetrics) {
+    List<String> keys = new ArrayList<>();
+    keys.add("#");
+    keys.add("TransformName");
+    keys.add("Copy");
+    for (IEngineMetric m : usedMetrics) {
+      keys.add(m.getCode());
+    }
+    keys.add("duration");
+    keys.add("speed");
+    keys.add("status");
+    return keys;
+  }
+
+  /** Saves current column widths to the session map so they can be restored 
after refresh. */
+  private void saveColumnWidths() {
+    if (pipelineGridView == null || pipelineGridView.isDisposed()) {
+      return;
+    }
+    if (metricsColumnKeys == null || metricsColumnKeys.size() == 0) {
+      return;
+    }
+    org.eclipse.swt.widgets.Table table = pipelineGridView.table;
+    int n = Math.min(table.getColumnCount(), metricsColumnKeys.size());
+    for (int i = 0; i < n; i++) {
+      int w = table.getColumn(i).getWidth();
+      if (w > 0) {
+        metricsColumnWidths.put(metricsColumnKeys.get(i), w);
+      }
+    }
+  }
+
+  /**
+   * Restores column widths from the session map onto the table. Needed for 
the "#" column (no
+   * ColumnInfo) and as a fallback so widths are applied after optWidth.
+   */
+  private void restoreColumnWidths(List<IEngineMetric> usedMetrics) {
+    if (pipelineGridView == null || pipelineGridView.isDisposed()) {
+      return;
+    }
+    restoringColumnWidths = true;
+    try {
+      List<String> keys = getColumnKeys(usedMetrics);
+      Table table = pipelineGridView.table;
+      int n = Math.min(table.getColumnCount(), keys.size());
+      for (int i = 0; i < n; i++) {
+        String key = keys.get(i);
+        Integer w = metricsColumnWidths.get(key);
+        if (w != null && w > 0) {
+          TableColumn tc = table.getColumn(i);
+          tc.setWidth(w);
+        }
+      }
+    } finally {
+      // Clear flag asynchronously so any Resize events queued by setWidth() 
are still ignored
+      Display display =
+          pipelineGridView != null && !pipelineGridView.isDisposed()
+              ? pipelineGridView.table.getDisplay()
+              : null;
+      if (display != null && !display.isDisposed()) {
+        display.asyncExec(() -> restoringColumnWidths = false);
+      } else {
+        restoringColumnWidths = false;
+      }
+    }
+  }
+
+  /** Attach resize listeners so user column resizes are stored for the 
session. */
+  private void attachColumnWidthListeners() {
+    if (pipelineGridView == null || pipelineGridView.isDisposed()) {
+      return;
+    }
+    if (metricsColumnKeys == null || metricsColumnKeys.size() == 0) {
+      return;
+    }
+    Table table = pipelineGridView.table;
+    for (int i = 0; i < table.getColumnCount() && i < 
metricsColumnKeys.size(); i++) {
+      final String key = metricsColumnKeys.get(i);
+      TableColumn col = table.getColumn(i);
+      col.addListener(
+          SWT.Resize,
+          e -> {
+            if (restoringColumnWidths) {
+              return;
+            }
+            if (!col.isDisposed()) {
+              metricsColumnWidths.put(key, col.getWidth());
+            }
+          });
+    }
   }
 
   private void fillTableRows(List<List<String>> componentStringsList, int 
errorColumnIndex) {
@@ -716,7 +1002,6 @@ public class HopGuiPipelineGridDelegate {
     }
     int errorsCol = 3 + errorColumnIndex; // row has #, name, copy, then 
metrics
     Color errorBg = GuiResource.getInstance().getColorLightRed();
-    Color white = GuiResource.getInstance().getColorWhite();
     for (int row = 0; row < componentStringsList.size(); row++) {
       List<String> componentStrings = componentStringsList.get(row);
       TableItem item;
@@ -730,7 +1015,7 @@ public class HopGuiPipelineGridDelegate {
       }
       if (errorColumnIndex >= 0 && errorsCol < componentStrings.size()) {
         long err = parseFormattedLong(componentStrings.get(errorsCol));
-        item.setBackground(err > 0 ? errorBg : white);
+        item.setBackground(err > 0 ? errorBg : null);
       }
     }
   }
@@ -897,6 +1182,9 @@ public class HopGuiPipelineGridDelegate {
     if (trimmed.isEmpty()) {
       return 0L;
     }
+    // Strip trailing unit suffix so we can parse the number (e.g. "1,234 
rows" or "1,234 r" ->
+    // 1234)
+    trimmed = trimmed.replaceAll("\\s+(rows|r|runs|flushes|rows/s)$", "");
     return Long.parseLong(trimmed.replace(",", ""));
   }
 
@@ -969,7 +1257,7 @@ public class HopGuiPipelineGridDelegate {
     if (baseTransform.getErrors() > 0) {
       row.setBackground(GuiResource.getInstance().getColorLightRed());
     } else {
-      row.setBackground(GuiResource.getInstance().getColorWhite());
+      row.setBackground(null);
     }
   }
 
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
index 8aab5e4b41..c593a8d58b 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
@@ -51,6 +51,7 @@ import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Canvas;
 import org.eclipse.swt.widgets.Combo;
 import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.ExpandBar;
@@ -95,6 +96,13 @@ public class ConfigGuiOptionsTab {
   private Button wDisableZoomScrolling;
   private Button wHideMenuBar;
   private Button wShowTableViewToolbar;
+  private Button wMetricsPanelShowUnits;
+  private Button wMetricsPanelShowInput;
+  private Button wMetricsPanelShowRead;
+  private Button wMetricsPanelShowOutput;
+  private Button wMetricsPanelShowUpdated;
+  private Button wMetricsPanelShowRejected;
+  private Button wMetricsPanelShowBuffersInput;
   private Combo wDefaultLocale;
 
   private boolean isReloading = false; // Flag to prevent saving during reload
@@ -165,6 +173,15 @@ public class ConfigGuiOptionsTab {
       wEnableInfiniteMove.setSelection(props.isInfiniteCanvasMoveEnabled());
       wHideMenuBar.setSelection(props.isHidingMenuBar());
       wShowTableViewToolbar.setSelection(props.isShowTableViewToolbar());
+      if (wMetricsPanelShowUnits != null && 
!wMetricsPanelShowUnits.isDisposed()) {
+        wMetricsPanelShowUnits.setSelection(props.isMetricsPanelShowUnits());
+        wMetricsPanelShowInput.setSelection(props.isMetricsPanelShowInput());
+        wMetricsPanelShowRead.setSelection(props.isMetricsPanelShowRead());
+        wMetricsPanelShowOutput.setSelection(props.isMetricsPanelShowOutput());
+        
wMetricsPanelShowUpdated.setSelection(props.isMetricsPanelShowUpdated());
+        
wMetricsPanelShowRejected.setSelection(props.isMetricsPanelShowRejected());
+        
wMetricsPanelShowBuffersInput.setSelection(props.isMetricsPanelShowBuffersInput());
+      }
       // On macOS (and other non-Windows), dark mode follows system; sync from 
system so UI and
       // props match. In Web environment, isSystemDarkTheme() is not available.
       boolean darkMode;
@@ -727,6 +744,128 @@ public class ConfigGuiOptionsTab {
                       }
                     }));
 
+    lastControl = tablesExpandBar;
+
+    // Metrics panel section - using ExpandBar
+    ExpandBar metricsPanelExpandBar = new ExpandBar(wLookComp, SWT.V_SCROLL);
+    PropsUi.setLook(metricsPanelExpandBar);
+
+    FormData fdMetricsPanelExpandBar = new FormData();
+    fdMetricsPanelExpandBar.left = new FormAttachment(0, 0);
+    fdMetricsPanelExpandBar.right = new FormAttachment(100, 0);
+    fdMetricsPanelExpandBar.top = new FormAttachment(lastControl, 2 * margin);
+    metricsPanelExpandBar.setLayoutData(fdMetricsPanelExpandBar);
+
+    Composite metricsPanelContent = new Composite(metricsPanelExpandBar, 
SWT.NONE);
+    PropsUi.setLook(metricsPanelContent);
+    FormLayout metricsPanelLayout = new FormLayout();
+    metricsPanelLayout.marginWidth = PropsUi.getFormMargin();
+    metricsPanelLayout.marginHeight = PropsUi.getFormMargin();
+    metricsPanelContent.setLayout(metricsPanelLayout);
+
+    Control lastMetricsPanelControl = null;
+    wMetricsPanelShowUnits =
+        createCheckbox(
+            metricsPanelContent,
+            "EnterOptionsDialog.MetricsPanel.ShowUnits.Label",
+            "EnterOptionsDialog.MetricsPanel.ShowUnits.ToolTip",
+            props.isMetricsPanelShowUnits(),
+            lastMetricsPanelControl,
+            margin);
+    lastMetricsPanelControl = wMetricsPanelShowUnits;
+
+    wMetricsPanelShowInput =
+        createCheckbox(
+            metricsPanelContent,
+            "EnterOptionsDialog.MetricsPanel.ShowInput.Label",
+            null,
+            props.isMetricsPanelShowInput(),
+            lastMetricsPanelControl,
+            margin);
+    lastMetricsPanelControl = wMetricsPanelShowInput;
+
+    wMetricsPanelShowRead =
+        createCheckbox(
+            metricsPanelContent,
+            "EnterOptionsDialog.MetricsPanel.ShowRead.Label",
+            null,
+            props.isMetricsPanelShowRead(),
+            lastMetricsPanelControl,
+            margin);
+    lastMetricsPanelControl = wMetricsPanelShowRead;
+
+    wMetricsPanelShowOutput =
+        createCheckbox(
+            metricsPanelContent,
+            "EnterOptionsDialog.MetricsPanel.ShowOutput.Label",
+            null,
+            props.isMetricsPanelShowOutput(),
+            lastMetricsPanelControl,
+            margin);
+    lastMetricsPanelControl = wMetricsPanelShowOutput;
+
+    wMetricsPanelShowUpdated =
+        createCheckbox(
+            metricsPanelContent,
+            "EnterOptionsDialog.MetricsPanel.ShowUpdated.Label",
+            null,
+            props.isMetricsPanelShowUpdated(),
+            lastMetricsPanelControl,
+            margin);
+    lastMetricsPanelControl = wMetricsPanelShowUpdated;
+
+    wMetricsPanelShowRejected =
+        createCheckbox(
+            metricsPanelContent,
+            "EnterOptionsDialog.MetricsPanel.ShowRejected.Label",
+            null,
+            props.isMetricsPanelShowRejected(),
+            lastMetricsPanelControl,
+            margin);
+    lastMetricsPanelControl = wMetricsPanelShowRejected;
+
+    wMetricsPanelShowBuffersInput =
+        createCheckbox(
+            metricsPanelContent,
+            "EnterOptionsDialog.MetricsPanel.ShowBuffersInput.Label",
+            null,
+            props.isMetricsPanelShowBuffersInput(),
+            lastMetricsPanelControl,
+            margin);
+    lastMetricsPanelControl = wMetricsPanelShowBuffersInput;
+
+    ExpandItem metricsPanelItem = new ExpandItem(metricsPanelExpandBar, 
SWT.NONE);
+    metricsPanelItem.setText(
+        BaseMessages.getString(PKG, 
"EnterOptionsDialog.Section.MetricsPanel"));
+    metricsPanelItem.setControl(metricsPanelContent);
+    metricsPanelItem.setHeight(metricsPanelContent.computeSize(SWT.DEFAULT, 
SWT.DEFAULT).y);
+    metricsPanelItem.setExpanded(true);
+
+    metricsPanelExpandBar.addListener(
+        SWT.Expand,
+        e ->
+            Display.getDefault()
+                .asyncExec(
+                    () -> {
+                      if (!wLookComp.isDisposed() && !sLookComp.isDisposed()) {
+                        wLookComp.layout();
+                        
sLookComp.setMinHeight(wLookComp.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);
+                      }
+                    }));
+    metricsPanelExpandBar.addListener(
+        SWT.Collapse,
+        e ->
+            Display.getDefault()
+                .asyncExec(
+                    () -> {
+                      if (!wLookComp.isDisposed() && !sLookComp.isDisposed()) {
+                        wLookComp.layout();
+                        
sLookComp.setMinHeight(wLookComp.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);
+                      }
+                    }));
+
+    lastControl = metricsPanelExpandBar;
+
     FormData fdLookComp = new FormData();
     fdLookComp.left = new FormAttachment(0, 0);
     fdLookComp.right = new FormAttachment(100, 0);
@@ -957,6 +1096,13 @@ public class ConfigGuiOptionsTab {
     props.setDarkMode(darkMode);
     props.setHidingMenuBar(wHideMenuBar.getSelection());
     props.setShowTableViewToolbar(wShowTableViewToolbar.getSelection());
+    props.setMetricsPanelShowUnits(wMetricsPanelShowUnits.getSelection());
+    props.setMetricsPanelShowInput(wMetricsPanelShowInput.getSelection());
+    props.setMetricsPanelShowRead(wMetricsPanelShowRead.getSelection());
+    props.setMetricsPanelShowOutput(wMetricsPanelShowOutput.getSelection());
+    props.setMetricsPanelShowUpdated(wMetricsPanelShowUpdated.getSelection());
+    
props.setMetricsPanelShowRejected(wMetricsPanelShowRejected.getSelection());
+    
props.setMetricsPanelShowBuffersInput(wMetricsPanelShowBuffersInput.getSelection());
 
     int defaultLocaleIndex = wDefaultLocale.getSelectionIndex();
     if (defaultLocaleIndex < 0 || defaultLocaleIndex >= 
GlobalMessages.localeCodes.length) {
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/PipelineExecutionViewer.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/PipelineExecutionViewer.java
index 7b8e874e99..37b3039b66 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/PipelineExecutionViewer.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/PipelineExecutionViewer.java
@@ -83,6 +83,7 @@ import org.apache.hop.ui.hopgui.HopGui;
 import org.apache.hop.ui.hopgui.HopGuiExtensionPoint;
 import org.apache.hop.ui.hopgui.ToolbarFacade;
 import org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
+import org.apache.hop.ui.hopgui.file.pipeline.PipelineMetricDisplayUtil;
 import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
 import org.apache.hop.ui.hopgui.shared.BaseExecutionViewer;
 import org.apache.hop.ui.hopgui.shared.CanvasZoomHelper;
@@ -516,8 +517,13 @@ public class PipelineExecutionViewer extends 
BaseExecutionViewer
       Set<String> metricNames,
       IEngineMetric metric) {
     if (metricNames.contains(metric.getHeader())) {
-      columns.add(new ColumnInfo(metric.getHeader(), 
ColumnInfo.COLUMN_TYPE_TEXT, true, true));
-      // Index +1 because of the left-hand row number
+      columns.add(
+          new ColumnInfo(
+              PipelineMetricDisplayUtil.getDisplayHeaderWithUnit(metric),
+              ColumnInfo.COLUMN_TYPE_TEXT,
+              true,
+              true));
+      // Index +1 because of the left-hand row number; use raw header for 
lookup
       indexMap.put(metric.getHeader(), columns.size());
     }
   }
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
index d6ac80b3c6..d424e9ca57 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
@@ -88,6 +88,15 @@ EnterOptionsDialog.Section.Appearance=Appearance
 EnterOptionsDialog.Section.GeneralAppearance=General appearance
 EnterOptionsDialog.Section.PipelineWorkflowCanvas=Pipeline and Workflow canvas
 EnterOptionsDialog.Section.TablesGrids=Tables and grids
+EnterOptionsDialog.Section.MetricsPanel=Metrics panel
+EnterOptionsDialog.MetricsPanel.ShowUnits.Label=Show units in cells
+EnterOptionsDialog.MetricsPanel.ShowUnits.ToolTip=When enabled, units (e.g. r, 
h:m:s, rows/s) are shown in the pipeline metrics grid cells.
+EnterOptionsDialog.MetricsPanel.ShowInput.Label=Show Input
+EnterOptionsDialog.MetricsPanel.ShowRead.Label=Show Read
+EnterOptionsDialog.MetricsPanel.ShowOutput.Label=Show Output
+EnterOptionsDialog.MetricsPanel.ShowUpdated.Label=Show Updated
+EnterOptionsDialog.MetricsPanel.ShowRejected.Label=Show Rejected
+EnterOptionsDialog.MetricsPanel.ShowBuffersInput.Label=Show Buffers Input
 EnterOptionsDialog.SplitHopsConfirm.Label=Show confirmation when splitting hops
 EnterOptionsDialog.SplitHopsConfirm.Tooltip=If a transform is drawn on a hop, 
the hop can be split so that the new transform lays between the two original 
ones.\nIf this option is enabled, a confirmation dialog will be shown before 
splitting.
 EnterOptionsDialog.SaveConfirm.Label=Show confirmation to save file when 
starting pipeline or workflow
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
index dd6117e962..009a82b344 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
@@ -227,23 +227,34 @@ PipelineLog.Button.ShowErrorLines=\ &Show error lines
 PipelineLog.Button.ShowOnlyActiveTransforms=Hide inactive
 PipelineLog.Button.ShowOnlySelectedTransforms=Show only selected transforms
 PipelineLog.Search.TransformName.Placeholder=Filter by transform name...
+PipelineLog.MetricsView.View=View
+PipelineLog.MetricsView.View.Tooltip=Show or hide columns and units in the 
metrics grid
+PipelineLog.MetricsView.ShowUnits=Show units in cells
+PipelineLog.MetricsView.ShowInput=Show Input
+PipelineLog.MetricsView.ShowRead=Show Read
+PipelineLog.MetricsView.ShowOutput=Show Output
+PipelineLog.MetricsView.ShowUpdated=Show Updated
+PipelineLog.MetricsView.ShowRejected=Show Rejected
+PipelineLog.MetricsView.ShowBuffersInput=Show Buffers Input
 PipelineLog.Column.Active=Active
-PipelineLog.Column.BuffersInput=Buffers Input
-PipelineLog.Column.BuffersOutput=Buffers Output
+PipelineLog.Column.BuffersInput=Buffers Input (rows)
+PipelineLog.Column.BuffersOutput=Buffers Output (rows)
 PipelineLog.Column.Copynr=Copy
-PipelineLog.Column.Duration=Duration
-PipelineLog.Column.Errors=Errors
-PipelineLog.Column.Input=Input
-PipelineLog.Column.Output=Output
+PipelineLog.Column.Duration=Duration (h:m:s)
+PipelineLog.Column.DurationNoUnit=Duration
+PipelineLog.Column.SpeedNoUnit=Speed
+PipelineLog.Column.Errors=Errors (rows)
+PipelineLog.Column.Input=Input (rows)
+PipelineLog.Column.Output=Output (rows)
 PipelineLog.Column.Status=Status
 PipelineLog.Column.PriorityBufferSizes=input/output
-PipelineLog.Column.Read=Read
-PipelineLog.Column.Rejected=Rejected
-PipelineLog.Column.Speed=Speed
+PipelineLog.Column.Read=Read (rows)
+PipelineLog.Column.Rejected=Rejected (rows)
+PipelineLog.Column.Speed=Speed (rows/s)
 PipelineLog.Column.Time=Time
 PipelineLog.Column.TransformName=Transform Name
-PipelineLog.Column.Updated=Updated
-PipelineLog.Column.Written=Written
+PipelineLog.Column.Updated=Updated (rows)
+PipelineLog.Column.Written=Written (rows)
 PipelineLog.Dialog.DoNoPreviewWhileRunning.Message=Sorry, it is not possible 
to preview transforms when the pipeline is running.
 PipelineLog.Dialog.DoNoPreviewWhileRunning.Title=Sorry
 PipelineLog.Dialog.DoNoStartPipelineTwice.Message=This pipeline is already 
running, and must complete before it can run again.

Reply via email to