Repository: buildbuddy-io/buildbuddy
Pull request #8678: timing profile: add unit to some time series lanes
Author: login: sluongng
Created: 2025-03-18T13:13:41Z | Merged: 2025-03-18T16:36:44Z

Some time series lanes, such as "Memory usage" and "Network usage"
lanes can display a wide range of metric values that made it hard for
users to "guess" the unit being recorded.

Override the event name in Bazel profile with our own display name with
a unit attached to make it easier to read these metrics.



Diff:
diff --git a/app/trace/trace_events.ts b/app/trace/trace_events.ts
index 2198ec07ebf..638a393d837 100644
--- a/app/trace/trace_events.ts
+++ b/app/trace/trace_events.ts
@@ -46,44 +46,60 @@ export type TimeSeriesEvent = TraceEvent & {
 export type TimeSeries = {
   name: string;
   events: TimeSeriesEvent[];
+  unit?: string;
+};
+
+type SeriesMetadata = {
+  argKey: string;
+  displayName?: string;
+  unit?: string;
 };
 
 // A list of names of events that contain a timestamp and a value in args.
 //
+// The names from Bazel profiles will be displayed as-is, unless a displayName
+// is provided in the seriesMetadata object.
+//
 // We only render timeseries which are defined in this list. The order of the
 // entries in this list determines the order in which the timeseries are
 // displayed in the trace viewer panel.
 //
-// An example event for Bazel CPU usage looks like this: {"name": "CPU usage
-// (bazel)", ..., "args": {"cpu": 0.84}}
-const TIME_SERIES_EVENT_NAMES_AND_ARG_KEYS_ENTRIES: [string, string][] = [
+// An example event for Bazel CPU usage looks like this:
+// {
+//   "name": "CPU usage (bazel)",
+//   ...,
+//   "args": {"cpu": 0.84}
+// }
+const TIME_SERIES_METADATA = new Map<string, SeriesMetadata>([
   // Event names/arg keys from Bazel profiles.
   // These are defined by bazel / not controlled by us.
-  ["action count", "action"],
-  ["CPU usage (Bazel)", "cpu"],
-  ["Memory usage (Bazel)", "memory"],
-  ["CPU usage (total)", "system cpu"],
-  ["Memory usage (total)", "system memory"],
-  ["System load average", "load"],
-  ["Network Up usage (total)", "system network up (Mbps)"],
-  ["Network Down usage (total)", "system network down (Mbps)"],
+  ["action count", { argKey: "action" }],
+  ["CPU usage (Bazel)", { argKey: "cpu", unit: "cores" }],
+  ["Memory usage (Bazel)", { argKey: "memory", unit: "MB" }],
+  ["CPU usage (total)", { argKey: "system cpu", displayName: "CPU usage (System)", unit: "cores" }],
+  ["Memory usage (total)", { argKey: "system memory", unit: "MB" }],
+  ["System load average", { argKey: "load" }],
+  [
+    "Network Up usage (total)",
+    { argKey: "system network up (Mbps)", displayName: "Network Up usage (System)", unit: "Mbps" },
+  ],
+  [
+    "Network Down usage (total)",
+    { argKey: "system network down (Mbps)", displayName: "Network Down usage (System)", unit: "Mbps" },
+  ],
 
   // Event names/arg keys from executor profiles.
   // These are controlled by us, and defined in
   // enterprise/server/execution_service/execution_service.go
-  ["CPU usage (cores)", "cpu"],
-  ["Memory usage (KB)", "memory"],
-  ["Disk read bandwidth (MB/s)", "disk-read-bw"],
-  ["Disk read IOPS", "disk-read-iops"],
-  ["Disk write bandwidth (MB/s)", "disk-write-bw"],
-  ["Disk write IOPS", "disk-write-iops"],
-];
-
-const TIME_SERIES_EVENT_ORDER = new Map<string, number>(
-  TIME_SERIES_EVENT_NAMES_AND_ARG_KEYS_ENTRIES.map(([eventName, _argKey], index) => [eventName, index])
-);
+  ["CPU usage (cores)", { argKey: "cpu" }],
+  ["Memory usage (KB)", { argKey: "memory" }],
+  ["Disk read bandwidth (MB/s)", { argKey: "disk-read-bw" }],
+  ["Disk read IOPS", { argKey: "disk-read-iops" }],
+  ["Disk write bandwidth (MB/s)", { argKey: "disk-write-bw" }],
+  ["Disk write IOPS", { argKey: "disk-write-iops" }],
+]);
 
-const TIME_SERIES_EVENT_NAMES_AND_ARG_KEYS = new Map(TIME_SERIES_EVENT_NAMES_AND_ARG_KEYS_ENTRIES);
+const TIME_SERIES_EVENT_ORDER = new Map(Array.from(TIME_SERIES_METADATA).map(([name], index) => [name, index]));
 
 export async function readProfile(
   body: ReadableStream<Uint8Array>,
@@ -221,7 +237,7 @@ function normalizeThreadNames(events: TraceEvent[]) {
 }
 
 export function buildTimeSeries(events: TraceEvent[]): TimeSeries[] {
-  events = events.filter((event) => TIME_SERIES_EVENT_NAMES_AND_ARG_KEYS.has(event.name));
+  events = events.filter((event) => TIME_SERIES_METADATA.has(event.name));
   events.sort(timeSeriesEventComparator);
 
   const timelines: TimeSeries[] = [];
@@ -232,13 +248,14 @@ export function buildTimeSeries(events: TraceEvent[]): TimeSeries[] {
       // Encountered new type of time series data
       name = event.name;
       timeSeries = {
-        name,
+        name: TIME_SERIES_METADATA.get(name)?.displayName || name,
         events: [],
+        unit: TIME_SERIES_METADATA.get(name)?.unit,
       };
       timelines.push(timeSeries);
     }
     for (const key in event.args) {
-      if (key == TIME_SERIES_EVENT_NAMES_AND_ARG_KEYS.get(event.name)) {
+      if (key == TIME_SERIES_METADATA.get(event.name)?.argKey) {
         event.value = event.args[key];
       }
     }
diff --git a/app/trace/trace_viewer_model.ts b/app/trace/trace_viewer_model.ts
index 2848c27ad53..95e1113ec7a 100644
--- a/app/trace/trace_viewer_model.ts
+++ b/app/trace/trace_viewer_model.ts
@@ -39,6 +39,7 @@ export type LinePlotModel = {
   yMax: number;
   darkColor: string;
   lightColor: string;
+  unit?: string;
 };
 
 export function buildTraceViewerModel(trace: Profile, fitToContent?: boolean): TraceViewerModel {
@@ -115,7 +116,7 @@ function buildLinePlotsPanel(events: TraceEvent[], fitToContent?: boolean): Pane
     constants.TIME_SERIES_HEIGHT +
     constants.SECTION_PADDING_BOTTOM;
   const sections: SectionModel[] = [];
-  for (const { name, events } of timeSeries) {
+  for (const { name, events, unit } of timeSeries) {
     const xs: number[] = [];
     const ys: number[] = [];
     let yMax = 0;
@@ -137,6 +138,7 @@ function buildLinePlotsPanel(events: TraceEvent[], fitToContent?: boolean): Pane
         yMax,
         darkColor: getMaterialChartColor(index),
         lightColor: getLightMaterialChartColor(index),
+        unit,
       },
     });
     sectionY += sectionHeight;
diff --git a/app/trace/trace_viewer_panel.ts b/app/trace/trace_viewer_panel.ts
index ab7ff189786..213e125d724 100644
--- a/app/trace/trace_viewer_panel.ts
+++ b/app/trace/trace_viewer_panel.ts
@@ -241,7 +241,8 @@ export default class Panel {
         constants.TIME_SERIES_HEIGHT;
       const pointClientY = plotClientBottom - (mouseModelY / section.linePlot.yMax) * constants.TIME_SERIES_HEIGHT;
       fillCircle(ctx, x, pointClientY, 2, section.linePlot.darkColor);
-      fillTextBox(ctx, String(truncateDecimals(mouseModelY, 2)), textBoxX, pointClientY - 6, {
+      const unit = section.linePlot.unit ? " " + section.linePlot.unit : "";
+      fillTextBox(ctx, String(truncateDecimals(mouseModelY, 2)) + unit, textBoxX, pointClientY - 6, {
         boxPadding: 1,
         boxRadius: 8,
         xAnchor,


Reviews:
user: login: fmeum
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T13:36:24Z
user: login: sluongng
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T13:45:40Z
user: login: bduffany
body: nit: instead of showing the unit in the section titles, it might be slightly nicer to show the units right after the value (when it is displayed on hover), so that the user doesn't have to spend any effort cross-referencing the value with the lane title in order to interpret the value.

Here is where the value is rendered (where you could append the unit): https://github.com/buildbuddy-io/buildbuddy/blob/5b7f2e26223fc8d1ba5be236813abc9a4fdf4085/app/trace/trace_viewer_panel.ts#L244

You could add a `unit: string` field to LinePlotModel here: https://github.com/buildbuddy-io/buildbuddy/blob/5b7f2e26223fc8d1ba5be236813abc9a4fdf4085/app/trace/trace_viewer_model.ts#L36-L42

The unit could be set on the `linePlot` here, depending on the `name`: https://github.com/buildbuddy-io/buildbuddy/blob/5b7f2e26223fc8d1ba5be236813abc9a4fdf4085/app/trace/trace_viewer_model.ts#L130-L141
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T14:46:48Z
user: login: bduffany
body: nice
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-18T15:33:39Z
user: login: bduffany
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T15:34:33Z
user: login: sluongng
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T16:08:06Z
user: login: sluongng
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T16:08:33Z
user: login: sluongng
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T16:10:10Z
user: login: bduffany
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-18T16:24:45Z

Review comments:
diff_hunk: @@ -48,42 +48,53 @@ export type TimeSeries = {
   events: TimeSeriesEvent[];
 };
 
+type seriesMetadata = {
+  argKey: string;
+  displayName?: string;
+};
+
 // A list of names of events that contain a timestamp and a value in args.
 //
+// The names from Bazel profiles will be displayed as-is, unless a displayName
+// is provided in the seriesMetadata object.
+//
 // We only render timeseries which are defined in this list. The order of the
 // entries in this list determines the order in which the timeseries are
 // displayed in the trace viewer panel.
 //
-// An example event for Bazel CPU usage looks like this: {"name": "CPU usage
-// (bazel)", ..., "args": {"cpu": 0.84}}
-const TIME_SERIES_EVENT_NAMES_AND_ARG_KEYS_ENTRIES: [string, string][] = [
+// An example event for Bazel CPU usage looks like this:
+// {
+//   "name": "CPU usage (bazel)",
+//   ...,
+//   "args": {"cpu": 0.84}
+// }
+const TIME_SERIES_EVENT_NAMES = new Map<string, seriesMetadata>([
   // Event names/arg keys from Bazel profiles.
   // These are defined by bazel / not controlled by us.
-  ["action count", "action"],
-  ["CPU usage (Bazel)", "cpu"],
-  ["Memory usage (Bazel)", "memory"],
-  ["CPU usage (total)", "system cpu"],
-  ["Memory usage (total)", "system memory"],
-  ["System load average", "load"],
-  ["Network Up usage (total)", "system network up (Mbps)"],
-  ["Network Down usage (total)", "system network down (Mbps)"],
+  ["action count", { argKey: "action" }],
+  ["CPU usage (Bazel)", { argKey: "cpu" }],
+  ["Memory usage (Bazel)", { argKey: "memory", displayName: "Memory usage (Bazel) (MBytes)" }],
path: app/trace/trace_events.ts
user: login: fmeum
body: ```suggestion
  ["Memory usage (Bazel)", { argKey: "memory", displayName: "Memory usage (Bazel, in MB)" }],
```
and below
created_at: 2025-03-18T13:34:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -48,42 +48,53 @@ export type TimeSeries = {
   events: TimeSeriesEvent[];
 };
 
+type seriesMetadata = {
+  argKey: string;
+  displayName?: string;
+};
+
 // A list of names of events that contain a timestamp and a value in args.
 //
+// The names from Bazel profiles will be displayed as-is, unless a displayName
+// is provided in the seriesMetadata object.
+//
 // We only render timeseries which are defined in this list. The order of the
 // entries in this list determines the order in which the timeseries are
 // displayed in the trace viewer panel.
 //
-// An example event for Bazel CPU usage looks like this: {"name": "CPU usage
-// (bazel)", ..., "args": {"cpu": 0.84}}
-const TIME_SERIES_EVENT_NAMES_AND_ARG_KEYS_ENTRIES: [string, string][] = [
+// An example event for Bazel CPU usage looks like this:
+// {
+//   "name": "CPU usage (bazel)",
+//   ...,
+//   "args": {"cpu": 0.84}
+// }
+const TIME_SERIES_EVENT_NAMES = new Map<string, seriesMetadata>([
   // Event names/arg keys from Bazel profiles.
   // These are defined by bazel / not controlled by us.
-  ["action count", "action"],
-  ["CPU usage (Bazel)", "cpu"],
-  ["Memory usage (Bazel)", "memory"],
-  ["CPU usage (total)", "system cpu"],
-  ["Memory usage (total)", "system memory"],
-  ["System load average", "load"],
-  ["Network Up usage (total)", "system network up (Mbps)"],
-  ["Network Down usage (total)", "system network down (Mbps)"],
+  ["action count", { argKey: "action" }],
+  ["CPU usage (Bazel)", { argKey: "cpu" }],
+  ["Memory usage (Bazel)", { argKey: "memory", displayName: "Memory usage (Bazel) (MBytes)" }],
path: app/trace/trace_events.ts
user: login: sluongng
body: Done
created_at: 2025-03-18T13:45:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,44 +46,60 @@ export type TimeSeriesEvent = TraceEvent & {
 export type TimeSeries = {
   name: string;
   events: TimeSeriesEvent[];
+  unit?: string;
+};
+
+type seriesMetadata = {
path: app/trace/trace_events.ts
user: login: bduffany
body: (lowercase typename has no effect in TypeScript)

```suggestion
type SeriesMetadata = {
```
created_at: 2025-03-18T15:26:51Z
author_association: MEMBER
diff_hunk: @@ -46,44 +46,60 @@ export type TimeSeriesEvent = TraceEvent & {
 export type TimeSeries = {
   name: string;
   events: TimeSeriesEvent[];
+  unit?: string;
+};
+
+type seriesMetadata = {
+  argKey: string;
+  displayName?: string;
+  unit?: string;
 };
 
 // A list of names of events that contain a timestamp and a value in args.
 //
+// The names from Bazel profiles will be displayed as-is, unless a displayName
+// is provided in the seriesMetadata object.
+//
 // We only render timeseries which are defined in this list. The order of the
 // entries in this list determines the order in which the timeseries are
 // displayed in the trace viewer panel.
 //
-// An example event for Bazel CPU usage looks like this: {"name": "CPU usage
-// (bazel)", ..., "args": {"cpu": 0.84}}
-const TIME_SERIES_EVENT_NAMES_AND_ARG_KEYS_ENTRIES: [string, string][] = [
+// An example event for Bazel CPU usage looks like this:
+// {
+//   "name": "CPU usage (bazel)",
+//   ...,
+//   "args": {"cpu": 0.84}
+// }
+const TIME_SERIES_EVENT_NAMES = new Map<string, seriesMetadata>([
   // Event names/arg keys from Bazel profiles.
   // These are defined by bazel / not controlled by us.
-  ["action count", "action"],
-  ["CPU usage (Bazel)", "cpu"],
-  ["Memory usage (Bazel)", "memory"],
-  ["CPU usage (total)", "system cpu"],
-  ["Memory usage (total)", "system memory"],
-  ["System load average", "load"],
-  ["Network Up usage (total)", "system network up (Mbps)"],
-  ["Network Down usage (total)", "system network down (Mbps)"],
+  ["action count", { argKey: "action" }],
+  ["CPU usage (Bazel)", { argKey: "cpu" }],
+  ["Memory usage (Bazel)", { argKey: "memory", unit: "MB" }],
+  ["CPU usage (total)", { argKey: "system cpu", displayName: "CPU usage (System)" }],
+  ["Memory usage (total)", { argKey: "system memory", unit: "MB" }],
+  ["System load average", { argKey: "load" }],
+  [
+    "Network Up usage (total)",
+    { argKey: "system network up (Mbps)", displayName: "Network Up usage (System)", unit: "Mbps" },
+  ],
+  [
+    "Network Down usage (total)",
+    { argKey: "system network down (Mbps)", displayName: "Network Down usage (System)", unit: "Mbps" },
+  ],
 
   // Event names/arg keys from executor profiles.
-  // These are controlled by us, and defined in
+  // These are controlled by us, {argKey: and defined in
path: app/trace/trace_events.ts
user: login: bduffany
body: ```suggestion
  // These are controlled by us, and defined in
```
created_at: 2025-03-18T15:29:06Z
author_association: MEMBER
diff_hunk: @@ -46,44 +46,60 @@ export type TimeSeriesEvent = TraceEvent & {
 export type TimeSeries = {
   name: string;
   events: TimeSeriesEvent[];
+  unit?: string;
+};
+
+type seriesMetadata = {
+  argKey: string;
+  displayName?: string;
+  unit?: string;
 };
 
 // A list of names of events that contain a timestamp and a value in args.
 //
+// The names from Bazel profiles will be displayed as-is, unless a displayName
+// is provided in the seriesMetadata object.
+//
 // We only render timeseries which are defined in this list. The order of the
 // entries in this list determines the order in which the timeseries are
 // displayed in the trace viewer panel.
 //
-// An example event for Bazel CPU usage looks like this: {"name": "CPU usage
-// (bazel)", ..., "args": {"cpu": 0.84}}
-const TIME_SERIES_EVENT_NAMES_AND_ARG_KEYS_ENTRIES: [string, string][] = [
+// An example event for Bazel CPU usage looks like this:
+// {
+//   "name": "CPU usage (bazel)",
+//   ...,
+//   "args": {"cpu": 0.84}
+// }
+const TIME_SERIES_EVENT_NAMES = new Map<string, seriesMetadata>([
path: app/trace/trace_events.ts
user: login: bduffany
body: 
```suggestion
const TIME_SERIES_METADATA = new Map<string, seriesMetadata>([
```
created_at: 2025-03-18T15:30:24Z
author_association: MEMBER
diff_hunk: @@ -46,44 +46,60 @@ export type TimeSeriesEvent = TraceEvent & {
 export type TimeSeries = {
   name: string;
   events: TimeSeriesEvent[];
+  unit?: string;
+};
+
+type seriesMetadata = {
+  argKey: string;
+  displayName?: string;
+  unit?: string;
 };
 
 // A list of names of events that contain a timestamp and a value in args.
 //
+// The names from Bazel profiles will be displayed as-is, unless a displayName
+// is provided in the seriesMetadata object.
+//
 // We only render timeseries which are defined in this list. The order of the
 // entries in this list determines the order in which the timeseries are
 // displayed in the trace viewer panel.
 //
-// An example event for Bazel CPU usage looks like this: {"name": "CPU usage
-// (bazel)", ..., "args": {"cpu": 0.84}}
-const TIME_SERIES_EVENT_NAMES_AND_ARG_KEYS_ENTRIES: [string, string][] = [
+// An example event for Bazel CPU usage looks like this:
+// {
+//   "name": "CPU usage (bazel)",
+//   ...,
+//   "args": {"cpu": 0.84}
+// }
+const TIME_SERIES_EVENT_NAMES = new Map<string, seriesMetadata>([
   // Event names/arg keys from Bazel profiles.
   // These are defined by bazel / not controlled by us.
-  ["action count", "action"],
-  ["CPU usage (Bazel)", "cpu"],
-  ["Memory usage (Bazel)", "memory"],
-  ["CPU usage (total)", "system cpu"],
-  ["Memory usage (total)", "system memory"],
-  ["System load average", "load"],
-  ["Network Up usage (total)", "system network up (Mbps)"],
-  ["Network Down usage (total)", "system network down (Mbps)"],
+  ["action count", { argKey: "action" }],
+  ["CPU usage (Bazel)", { argKey: "cpu" }],
path: app/trace/trace_events.ts
user: login: bduffany
body: wdyt about using "cores" as the unit for the CPU usage charts?
created_at: 2025-03-18T15:34:33Z
author_association: MEMBER
diff_hunk: @@ -46,44 +46,60 @@ export type TimeSeriesEvent = TraceEvent & {
 export type TimeSeries = {
   name: string;
   events: TimeSeriesEvent[];
+  unit?: string;
+};
+
+type seriesMetadata = {
path: app/trace/trace_events.ts
user: login: sluongng
body: Good call
created_at: 2025-03-18T16:08:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,44 +46,60 @@ export type TimeSeriesEvent = TraceEvent & {
 export type TimeSeries = {
   name: string;
   events: TimeSeriesEvent[];
+  unit?: string;
+};
+
+type seriesMetadata = {
+  argKey: string;
+  displayName?: string;
+  unit?: string;
 };
 
 // A list of names of events that contain a timestamp and a value in args.
 //
+// The names from Bazel profiles will be displayed as-is, unless a displayName
+// is provided in the seriesMetadata object.
+//
 // We only render timeseries which are defined in this list. The order of the
 // entries in this list determines the order in which the timeseries are
 // displayed in the trace viewer panel.
 //
-// An example event for Bazel CPU usage looks like this: {"name": "CPU usage
-// (bazel)", ..., "args": {"cpu": 0.84}}
-const TIME_SERIES_EVENT_NAMES_AND_ARG_KEYS_ENTRIES: [string, string][] = [
+// An example event for Bazel CPU usage looks like this:
+// {
+//   "name": "CPU usage (bazel)",
+//   ...,
+//   "args": {"cpu": 0.84}
+// }
+const TIME_SERIES_EVENT_NAMES = new Map<string, seriesMetadata>([
   // Event names/arg keys from Bazel profiles.
   // These are defined by bazel / not controlled by us.
-  ["action count", "action"],
-  ["CPU usage (Bazel)", "cpu"],
-  ["Memory usage (Bazel)", "memory"],
-  ["CPU usage (total)", "system cpu"],
-  ["Memory usage (total)", "system memory"],
-  ["System load average", "load"],
-  ["Network Up usage (total)", "system network up (Mbps)"],
-  ["Network Down usage (total)", "system network down (Mbps)"],
+  ["action count", { argKey: "action" }],
+  ["CPU usage (Bazel)", { argKey: "cpu" }],
path: app/trace/trace_events.ts
user: login: sluongng
body: Added. I will leave the executor profiles alone for now though.
created_at: 2025-03-18T16:08:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,44 +46,60 @@ export type TimeSeriesEvent = TraceEvent & {
 export type TimeSeries = {
   name: string;
   events: TimeSeriesEvent[];
+  unit?: string;
+};
+
+type seriesMetadata = {
+  argKey: string;
+  displayName?: string;
+  unit?: string;
 };
 
 // A list of names of events that contain a timestamp and a value in args.
 //
+// The names from Bazel profiles will be displayed as-is, unless a displayName
+// is provided in the seriesMetadata object.
+//
 // We only render timeseries which are defined in this list. The order of the
 // entries in this list determines the order in which the timeseries are
 // displayed in the trace viewer panel.
 //
-// An example event for Bazel CPU usage looks like this: {"name": "CPU usage
-// (bazel)", ..., "args": {"cpu": 0.84}}
-const TIME_SERIES_EVENT_NAMES_AND_ARG_KEYS_ENTRIES: [string, string][] = [
+// An example event for Bazel CPU usage looks like this:
+// {
+//   "name": "CPU usage (bazel)",
+//   ...,
+//   "args": {"cpu": 0.84}
+// }
+const TIME_SERIES_EVENT_NAMES = new Map<string, seriesMetadata>([
   // Event names/arg keys from Bazel profiles.
   // These are defined by bazel / not controlled by us.
-  ["action count", "action"],
-  ["CPU usage (Bazel)", "cpu"],
-  ["Memory usage (Bazel)", "memory"],
-  ["CPU usage (total)", "system cpu"],
-  ["Memory usage (total)", "system memory"],
-  ["System load average", "load"],
-  ["Network Up usage (total)", "system network up (Mbps)"],
-  ["Network Down usage (total)", "system network down (Mbps)"],
+  ["action count", { argKey: "action" }],
+  ["CPU usage (Bazel)", { argKey: "cpu" }],
+  ["Memory usage (Bazel)", { argKey: "memory", unit: "MB" }],
+  ["CPU usage (total)", { argKey: "system cpu", displayName: "CPU usage (System)" }],
+  ["Memory usage (total)", { argKey: "system memory", unit: "MB" }],
+  ["System load average", { argKey: "load" }],
+  [
+    "Network Up usage (total)",
+    { argKey: "system network up (Mbps)", displayName: "Network Up usage (System)", unit: "Mbps" },
+  ],
+  [
+    "Network Down usage (total)",
+    { argKey: "system network down (Mbps)", displayName: "Network Down usage (System)", unit: "Mbps" },
+  ],
 
   // Event names/arg keys from executor profiles.
-  // These are controlled by us, and defined in
+  // These are controlled by us, {argKey: and defined in
path: app/trace/trace_events.ts
user: login: sluongng
body: not sure how this slipped in there. I blame vim
created_at: 2025-03-18T16:10:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: sluongng
created_at: 2025-03-18T13:45:35Z
body: Current (master):
![image](https://github.com/user-attachments/assets/d24d64d9-de95-4edd-ad20-a17250d61abd)

New:
![image](https://github.com/user-attachments/assets/d0b60ceb-ed5b-4ccc-91af-64f044e08718)

author_association: CONTRIBUTOR
user: login: sluongng
created_at: 2025-03-18T15:14:26Z
body: @bduffany great pointers!

Now it looks like this

![image](https://github.com/user-attachments/assets/b512bfd1-9c4a-467e-a966-024ec3e2aa0c)

author_association: CONTRIBUTOR