Distinguish profile types and show different UI labels for each

This is a source of confusion, so let's make it go away by cleanly
labeling what the users are looking at.
While at it, small refactoring to avoid the stringified type.

Bug: 239044591
Change-Id: Iae46bd593e09429fee6f1bc532b85deffc3f9050
diff --git a/docs/case-studies/memory.md b/docs/case-studies/memory.md
index ce23193..de4e401 100644
--- a/docs/case-studies/memory.md
+++ b/docs/case-studies/memory.md
@@ -353,14 +353,14 @@
 
 The tabs that are available are
 
-* **space**: how many bytes were allocated but not freed at this callstack the
-  moment the dump was created.
-* **alloc\_space**: how many bytes were allocated (including ones freed at the
+* **Unreleased size**: how many bytes were allocated but not freed at this
+  callstack the moment the dump was created.
+* **Total size**: how many bytes were allocated (including ones freed at the
   moment of the dump) at this callstack
-* **objects**: how many allocations without matching frees were sampled at this
-  callstack.
-* **alloc\_objects**: how many allocations (including ones with matching frees)
-  were sampled at this callstack.
+* **Unreleased count**: how many allocations without matching frees were done at
+  this callstack.
+* **Total count**: how many allocations (including ones with matching frees)
+  were done at this callstack.
 
 The default view will show you all allocations that were done while the
 profile was running but that weren't freed (the **space** tab).
diff --git a/docs/data-sources/native-heap-profiler.md b/docs/data-sources/native-heap-profiler.md
index 0d9b5b9..720b806 100644
--- a/docs/data-sources/native-heap-profiler.md
+++ b/docs/data-sources/native-heap-profiler.md
@@ -80,13 +80,13 @@
 
 The resulting profile proto contains four views on the data
 
-* **space**: how many bytes were allocated but not freed at this callstack the
-  moment the dump was created.
-* **alloc\_space**: how many bytes were allocated (including ones freed at the
+* **Unreleased size**: how many bytes were allocated but not freed at this
+  callstack the moment the dump was created.
+* **Total size**: how many bytes were allocated (including ones freed at the
   moment of the dump) at this callstack
-* **objects**: how many allocations without matching frees were done at this
-  callstack.
-* **alloc\_objects**: how many allocations (including ones with matching frees)
+* **Unreleased count**: how many allocations without matching frees were done at
+  this callstack.
+* **Total count**: how many allocations (including ones with matching frees)
   were done at this callstack.
 
 _(Googlers: You can also open the gzipped protos using http://pprof/)_
diff --git a/src/trace_processor/dynamic/experimental_flamegraph_generator.cc b/src/trace_processor/dynamic/experimental_flamegraph_generator.cc
index e695c40..a1d7bc6 100644
--- a/src/trace_processor/dynamic/experimental_flamegraph_generator.cc
+++ b/src/trace_processor/dynamic/experimental_flamegraph_generator.cc
@@ -37,7 +37,7 @@
     return ExperimentalFlamegraphGenerator::ProfileType::kGraph;
   }
   if (profile_name == "native") {
-    return ExperimentalFlamegraphGenerator::ProfileType::kNative;
+    return ExperimentalFlamegraphGenerator::ProfileType::kHeapProfile;
   }
   if (profile_name == "perf") {
     return ExperimentalFlamegraphGenerator::ProfileType::kPerf;
@@ -335,9 +335,9 @@
   if (values.profile_type == ProfileType::kGraph) {
     auto* tracker = HeapGraphTracker::GetOrCreate(context_);
     table = tracker->BuildFlamegraph(values.ts, *values.upid);
-  } else if (values.profile_type == ProfileType::kNative) {
-    table = BuildNativeHeapProfileFlamegraph(context_->storage.get(),
-                                             *values.upid, values.ts);
+  } else if (values.profile_type == ProfileType::kHeapProfile) {
+    table = BuildHeapProfileFlamegraph(context_->storage.get(), *values.upid,
+                                       values.ts);
   } else if (values.profile_type == ProfileType::kPerf) {
     table = BuildNativeCallStackSamplingFlamegraph(
         context_->storage.get(), values.upid, values.upid_group,
diff --git a/src/trace_processor/dynamic/experimental_flamegraph_generator.h b/src/trace_processor/dynamic/experimental_flamegraph_generator.h
index 11e7f3f..20eb20d 100644
--- a/src/trace_processor/dynamic/experimental_flamegraph_generator.h
+++ b/src/trace_processor/dynamic/experimental_flamegraph_generator.h
@@ -28,7 +28,7 @@
 
 class ExperimentalFlamegraphGenerator : public DynamicTableGenerator {
  public:
-  enum class ProfileType { kGraph, kNative, kPerf };
+  enum class ProfileType { kGraph, kHeapProfile, kPerf };
 
   struct InputValues {
     ProfileType profile_type;
diff --git a/src/trace_processor/dynamic/flamegraph_construction_algorithms.cc b/src/trace_processor/dynamic/flamegraph_construction_algorithms.cc
index ec0e5d4..d13a78d 100644
--- a/src/trace_processor/dynamic/flamegraph_construction_algorithms.cc
+++ b/src/trace_processor/dynamic/flamegraph_construction_algorithms.cc
@@ -321,9 +321,9 @@
 }
 
 std::unique_ptr<tables::ExperimentalFlamegraphNodesTable>
-BuildNativeHeapProfileFlamegraph(TraceStorage* storage,
-                                 UniquePid upid,
-                                 int64_t timestamp) {
+BuildHeapProfileFlamegraph(TraceStorage* storage,
+                           UniquePid upid,
+                           int64_t timestamp) {
   const tables::HeapProfileAllocationTable& allocation_tbl =
       storage->heap_profile_allocation_table();
   // PASS OVER ALLOCATIONS:
diff --git a/src/trace_processor/dynamic/flamegraph_construction_algorithms.h b/src/trace_processor/dynamic/flamegraph_construction_algorithms.h
index f8ce87c..bc89145 100644
--- a/src/trace_processor/dynamic/flamegraph_construction_algorithms.h
+++ b/src/trace_processor/dynamic/flamegraph_construction_algorithms.h
@@ -29,9 +29,9 @@
 };
 
 std::unique_ptr<tables::ExperimentalFlamegraphNodesTable>
-BuildNativeHeapProfileFlamegraph(TraceStorage* storage,
-                                 UniquePid upid,
-                                 int64_t timestamp);
+BuildHeapProfileFlamegraph(TraceStorage* storage,
+                           UniquePid upid,
+                           int64_t timestamp);
 
 std::unique_ptr<tables::ExperimentalFlamegraphNodesTable>
 BuildNativeCallStackSamplingFlamegraph(
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 75af830..b90055f 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -23,6 +23,7 @@
   columnKey,
   TableColumn,
 } from '../frontend/pivot_table_redux_types';
+
 import {randomColor} from './colorizer';
 import {createEmptyState} from './empty_state';
 import {DEFAULT_VIEWING_OPTION, PERF_SAMPLES_KEY} from './flamegraph_util';
@@ -38,6 +39,7 @@
   OmniboxState,
   PivotTableReduxResult,
   PrimaryTrackSortKey,
+  ProfileType,
   RecordingTarget,
   SCROLLING_TRACK_GROUP,
   SortDirection,
@@ -666,7 +668,7 @@
 
   selectHeapProfile(
       state: StateDraft,
-      args: {id: number, upid: number, ts: number, type: string}): void {
+      args: {id: number, upid: number, ts: number, type: ProfileType}): void {
     state.currentSelection = {
       kind: 'HEAP_PROFILE',
       id: args.id,
@@ -685,7 +687,7 @@
 
   selectPerfSamples(
       state: StateDraft,
-      args: {id: number, upid: number, ts: number, type: string}): void {
+      args: {id: number, upid: number, ts: number, type: ProfileType}): void {
     state.currentSelection = {
       kind: 'PERF_SAMPLES',
       id: args.id,
@@ -706,7 +708,7 @@
     upids: number[],
     startNs: number,
     endNs: number,
-    type: string,
+    type: ProfileType,
     viewingOption: FlamegraphStateViewingOption
   }): void {
     state.currentFlamegraphState = {
diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts
index ebcafc1..26f1e2f 100644
--- a/ui/src/common/actions_unittest.ts
+++ b/ui/src/common/actions_unittest.ts
@@ -27,6 +27,7 @@
 import {
   InThreadTrackSortKey,
   PrimaryTrackSortKey,
+  ProfileType,
   SCROLLING_TRACK_GROUP,
   State,
   TraceUrlSource,
@@ -447,26 +448,24 @@
 
 test('perf samples open flamegraph', () => {
   const state = createEmptyState();
-  const perfType = 'perf';
 
   const afterSelectingPerf = produce(state, (draft) => {
     StateActions.selectPerfSamples(
-        draft, {id: 0, upid: 0, ts: 0, type: perfType});
+        draft, {id: 0, upid: 0, ts: 0, type: ProfileType.PERF_SAMPLE});
   });
 
   expect(assertExists(afterSelectingPerf.currentFlamegraphState).type)
-      .toBe(perfType);
+      .toBe(ProfileType.PERF_SAMPLE);
 });
 
 test('heap profile opens flamegraph', () => {
   const state = createEmptyState();
-  const heapType = 'graph';
 
   const afterSelectingPerf = produce(state, (draft) => {
     StateActions.selectHeapProfile(
-        draft, {id: 0, upid: 0, ts: 0, type: heapType});
+        draft, {id: 0, upid: 0, ts: 0, type: ProfileType.JAVA_HEAP_GRAPH});
   });
 
   expect(assertExists(afterSelectingPerf.currentFlamegraphState).type)
-      .toBe(heapType);
+      .toBe(ProfileType.JAVA_HEAP_GRAPH);
 });
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 7c32a9f..bfaa40f 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -148,6 +148,14 @@
   }
 }
 
+export enum ProfileType {
+  HEAP_PROFILE = 'heap_profile',
+  NATIVE_HEAP_PROFILE = 'heap_profile:libc.malloc',
+  JAVA_HEAP_PROFILE = 'heap_profile:com.android.art',
+  JAVA_HEAP_GRAPH = 'graph',
+  PERF_SAMPLE = 'perf',
+}
+
 export type FlamegraphStateViewingOption =
     'SPACE'|'ALLOC_SPACE'|'OBJECTS'|'ALLOC_OBJECTS'|'PERF_SAMPLES';
 
@@ -295,7 +303,7 @@
   id: number;
   upid: number;
   ts: number;
-  type: string;
+  type: ProfileType;
 }
 
 export interface PerfSamplesSelection {
@@ -303,7 +311,7 @@
   id: number;
   upid: number;
   ts: number;
-  type: string;
+  type: ProfileType;
 }
 
 export interface FlamegraphState {
@@ -311,7 +319,7 @@
   upids: number[];
   startNs: number;
   endNs: number;
-  type: string;
+  type: ProfileType;
   viewingOption: FlamegraphStateViewingOption;
   focusRegex: string;
   expandedCallsite?: CallsiteInfo;
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index 05d680a..bbbecb4 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -26,7 +26,7 @@
   SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY,
 } from '../common/flamegraph_util';
 import {NUM, STR} from '../common/query_result';
-import {CallsiteInfo, FlamegraphState} from '../common/state';
+import {CallsiteInfo, FlamegraphState, ProfileType} from '../common/state';
 import {toNs} from '../common/time';
 import {
   FlamegraphDetails,
@@ -42,6 +42,35 @@
 import {Controller} from './controller';
 import {globals} from './globals';
 
+export function profileType(s: string): ProfileType {
+  if (isProfileType(s)) {
+    return s;
+  }
+  if (s.startsWith('heap_profile')) {
+    return ProfileType.HEAP_PROFILE;
+  }
+  throw new Error('Unknown type ${s}');
+}
+
+function isProfileType(s: string): s is ProfileType {
+  return Object.values(ProfileType).includes(s as ProfileType);
+}
+
+function getFlamegraphType(type: ProfileType) {
+  switch (type) {
+    case ProfileType.HEAP_PROFILE:
+    case ProfileType.NATIVE_HEAP_PROFILE:
+    case ProfileType.JAVA_HEAP_PROFILE:
+      return 'native';
+    case ProfileType.JAVA_HEAP_GRAPH:
+      return 'graph';
+    case ProfileType.PERF_SAMPLE:
+      return 'perf';
+    default:
+      throw new Error(`Unexpected profile type ${profileType}`);
+  }
+}
+
 export interface FlamegraphControllerArgs {
   engine: Engine;
 }
@@ -122,7 +151,7 @@
         upids,
         startNs: toNs(area.startSec),
         endNs: toNs(area.endSec),
-        type: 'perf',
+        type: ProfileType.PERF_SAMPLE,
         viewingOption: PERF_SAMPLES_KEY,
       }));
     }
@@ -243,7 +272,7 @@
 
   async getFlamegraphData(
       baseKey: string, viewingOption: string, startNs: number, endNs: number,
-      upids: number[], type: string,
+      upids: number[], type: ProfileType,
       focusRegex: string): Promise<CallsiteInfo[]> {
     let currentData: CallsiteInfo[];
     const key = `${baseKey}-${viewingOption}`;
@@ -388,7 +417,7 @@
   }
 
   private async prepareViewsAndTables(
-      startNs: number, endNs: number, upids: number[], type: string,
+      startNs: number, endNs: number, upids: number[], type: ProfileType,
       focusRegex: string): Promise<string> {
     // Creating unique names for views so we can reuse and not delete them
     // for each marker.
@@ -396,11 +425,12 @@
     if (focusRegex !== '') {
       focusRegexConditional = `and focus_str = '${focusRegex}'`;
     }
+    const flamegraphType = getFlamegraphType(type);
 
     /*
      * TODO(octaviant) this branching should be eliminated for simplicity.
      */
-    if (type === 'perf') {
+    if (type === ProfileType.PERF_SAMPLE) {
       let upidConditional = `upid = ${upids[0]}`;
       if (upids.length > 1) {
         upidConditional =
@@ -411,8 +441,8 @@
           cumulative_alloc_size, cumulative_count, cumulative_alloc_count,
           size, alloc_size, count, alloc_count, source_file, line_number
           from experimental_flamegraph
-          where profile_type = '${type}' and ${startNs} <= ts and ts <= ${
-              endNs} and ${upidConditional}
+          where profile_type = '${flamegraphType}' and ${startNs} <= ts and
+              ts <= ${endNs} and ${upidConditional}
           ${focusRegexConditional}`);
     }
     return this.cache.getTableName(
@@ -420,7 +450,7 @@
           cumulative_alloc_size, cumulative_count, cumulative_alloc_count,
           size, alloc_size, count, alloc_count, source_file, line_number
           from experimental_flamegraph
-          where profile_type = '${type}'
+          where profile_type = '${flamegraphType}'
             and ts = ${endNs}
             and upid = ${upids[0]}
             ${focusRegexConditional}`);
@@ -439,7 +469,7 @@
   }
 
   async getFlamegraphMetadata(
-      type: string, startNs: number, endNs: number, upids: number[]) {
+      type: ProfileType, startNs: number, endNs: number, upids: number[]) {
     // Don't do anything if selection of the marker stayed the same.
     if ((this.lastSelectedFlamegraphState !== undefined &&
          ((this.lastSelectedFlamegraphState.startNs === startNs &&
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index e3ad2fe..513c5f4 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -23,7 +23,7 @@
 import {featureFlags, Flag, PERF_SAMPLE_FLAG} from '../common/feature_flags';
 import {HttpRpcEngine} from '../common/http_rpc_engine';
 import {NUM, NUM_NULL, QueryError, STR, STR_NULL} from '../common/query_result';
-import {defaultTraceTime, EngineMode} from '../common/state';
+import {defaultTraceTime, EngineMode, ProfileType} from '../common/state';
 import {TimeSpan, toNs, toNsCeil, toNsFloor} from '../common/time';
 import {resetEngineWorker, WasmEngineProxy} from '../common/wasm_engine_proxy';
 import {
@@ -65,6 +65,7 @@
 import {
   FlamegraphController,
   FlamegraphControllerArgs,
+  profileType,
 } from './flamegraph_controller';
 import {
   FlowEventsController,
@@ -437,22 +438,27 @@
     const row = profile.firstRow({ts: NUM, upid: NUM});
     const ts = row.ts;
     const upid = row.upid;
-    globals.dispatch(
-        Actions.selectPerfSamples({id: 0, upid, ts, type: 'perf'}));
+    globals.dispatch(Actions.selectPerfSamples(
+        {id: 0, upid, ts, type: ProfileType.PERF_SAMPLE}));
   }
 
   private async selectFirstHeapProfile() {
-    const query = `select * from
-    (select distinct(ts) as ts, 'native' as type,
-        upid from heap_profile_allocation
-        union
-        select distinct(graph_sample_ts) as ts, 'graph' as type, upid from
-        heap_graph_object) order by ts limit 1`;
+    const query = `select * from (
+      select
+        min(ts) AS ts,
+        'heap_profile:' || group_concat(distinct heap_name) AS type,
+        upid
+      from heap_profile_allocation
+      group by upid
+      union
+      select distinct graph_sample_ts as ts, 'graph' as type, upid
+      from heap_graph_object)
+      order by ts limit 1`;
     const profile = await assertExists(this.engine).query(query);
     if (profile.numRows() !== 1) return;
     const row = profile.firstRow({ts: NUM, type: STR, upid: NUM});
     const ts = row.ts;
-    const type = row.type;
+    const type = profileType(row.type);
     const upid = row.upid;
     globals.dispatch(Actions.selectHeapProfile({id: 0, upid, ts, type}));
   }
diff --git a/ui/src/frontend/flamegraph_panel.ts b/ui/src/frontend/flamegraph_panel.ts
index 5958cdd..cadee12 100644
--- a/ui/src/frontend/flamegraph_panel.ts
+++ b/ui/src/frontend/flamegraph_panel.ts
@@ -23,8 +23,13 @@
   PERF_SAMPLES_KEY,
   SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY,
 } from '../common/flamegraph_util';
-import {CallsiteInfo, FlamegraphStateViewingOption} from '../common/state';
+import {
+  CallsiteInfo,
+  FlamegraphStateViewingOption,
+  ProfileType,
+} from '../common/state';
 import {timeToCode} from '../common/time';
+import {profileType} from '../controller/flamegraph_controller';
 
 import {PerfettoMouseEvent} from './events';
 import {Flamegraph, NodeRendering} from './flamegraph';
@@ -40,23 +45,6 @@
 
 const HEADER_HEIGHT = 30;
 
-enum ProfileType {
-  NATIVE_HEAP_PROFILE = 'native',
-  JAVA_HEAP_GRAPH = 'graph',
-  PERF_SAMPLE = 'perf'
-}
-
-function isProfileType(s: string): s is ProfileType {
-  return Object.values(ProfileType).includes(s as ProfileType);
-}
-
-function toProfileType(s: string): ProfileType {
-  if (!isProfileType(s)) {
-    throw new Error('Unknown type ${s}');
-  }
-  return s;
-}
-
 function toSelectedCallsite(c: CallsiteInfo|undefined): string {
   if (c !== undefined && c.name !== undefined) {
     return c.name;
@@ -90,7 +78,7 @@
         flamegraphDetails.durNs !== undefined &&
         flamegraphDetails.pids !== undefined &&
         flamegraphDetails.upids !== undefined) {
-      this.profileType = toProfileType(flamegraphDetails.type);
+      this.profileType = profileType(flamegraphDetails.type);
       this.ts = flamegraphDetails.durNs;
       this.pids = flamegraphDetails.pids;
       if (flamegraphDetails.flamegraph) {
@@ -197,10 +185,14 @@
 
   private getTitle(): string {
     switch (this.profileType!) {
+      case ProfileType.HEAP_PROFILE:
+        return 'Heap profile:';
       case ProfileType.NATIVE_HEAP_PROFILE:
-        return 'Heap Profile:';
+        return 'Native heap profile:';
+      case ProfileType.JAVA_HEAP_PROFILE:
+        return 'Java heap profile:';
       case ProfileType.JAVA_HEAP_GRAPH:
-        return 'Java Heap:';
+        return 'Java heap graph:';
       case ProfileType.PERF_SAMPLE:
         return 'Profile:';
       default:
@@ -220,7 +212,9 @@
         } else {
           return RENDER_SELF_AND_TOTAL;
         }
+      case ProfileType.HEAP_PROFILE:
       case ProfileType.NATIVE_HEAP_PROFILE:
+      case ProfileType.JAVA_HEAP_PROFILE:
       case ProfileType.PERF_SAMPLE:
         return RENDER_SELF_AND_TOTAL;
       default:
@@ -293,21 +287,39 @@
   private static selectViewingOptions(profileType: ProfileType) {
     switch (profileType) {
       case ProfileType.PERF_SAMPLE:
-        return [this.buildButtonComponent(PERF_SAMPLES_KEY, 'samples')];
+        return [this.buildButtonComponent(PERF_SAMPLES_KEY, 'Samples')];
       case ProfileType.JAVA_HEAP_GRAPH:
         return [
           this.buildButtonComponent(
-              SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, 'space'),
-          this.buildButtonComponent(OBJECTS_ALLOCATED_NOT_FREED_KEY, 'objects'),
+              SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, 'Size'),
+          this.buildButtonComponent(OBJECTS_ALLOCATED_NOT_FREED_KEY, 'Objects'),
+        ];
+      case ProfileType.HEAP_PROFILE:
+        return [
+          this.buildButtonComponent(
+              SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, 'Unreleased size'),
+          this.buildButtonComponent(OBJECTS_ALLOCATED_NOT_FREED_KEY, 'Count'),
+          this.buildButtonComponent(
+              ALLOC_SPACE_MEMORY_ALLOCATED_KEY, 'Total size'),
+          this.buildButtonComponent(OBJECTS_ALLOCATED_KEY, 'Total count'),
         ];
       case ProfileType.NATIVE_HEAP_PROFILE:
         return [
           this.buildButtonComponent(
-              SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, 'space'),
-          this.buildButtonComponent(OBJECTS_ALLOCATED_NOT_FREED_KEY, 'objects'),
+              SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, 'Unreleased malloc size'),
           this.buildButtonComponent(
-              ALLOC_SPACE_MEMORY_ALLOCATED_KEY, 'alloc space'),
-          this.buildButtonComponent(OBJECTS_ALLOCATED_KEY, 'alloc objects'),
+              OBJECTS_ALLOCATED_NOT_FREED_KEY, 'Unreleased malloc count'),
+          this.buildButtonComponent(
+              ALLOC_SPACE_MEMORY_ALLOCATED_KEY, 'Total malloc size'),
+          this.buildButtonComponent(
+              OBJECTS_ALLOCATED_KEY, 'Total malloc count'),
+        ];
+      case ProfileType.JAVA_HEAP_PROFILE:
+        return [
+          this.buildButtonComponent(
+              ALLOC_SPACE_MEMORY_ALLOCATED_KEY, 'Total allocation size'),
+          this.buildButtonComponent(
+              OBJECTS_ALLOCATED_KEY, 'Total allocation count'),
         ];
       default:
         throw new Error(`Unexpected profile type ${profileType}`);
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 76423e9..7bb5ad2 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -24,7 +24,7 @@
 import {Engine} from '../common/engine';
 import {MetricResult} from '../common/metric_data';
 import {CurrentSearchResults, SearchSummary} from '../common/search_data';
-import {CallsiteInfo, EngineConfig, State} from '../common/state';
+import {CallsiteInfo, EngineConfig, ProfileType, State} from '../common/state';
 import {fromNs, toNs} from '../common/time';
 
 import {Analytics, initAnalytics} from './analytics';
@@ -121,7 +121,7 @@
 }
 
 export interface FlamegraphDetails {
-  type?: string;
+  type?: ProfileType;
   id?: number;
   startNs?: number;
   durNs?: number;
diff --git a/ui/src/tracks/heap_profile/index.ts b/ui/src/tracks/heap_profile/index.ts
index c0b3b44..b3eb40a 100644
--- a/ui/src/tracks/heap_profile/index.ts
+++ b/ui/src/tracks/heap_profile/index.ts
@@ -12,12 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {ProfileType} from 'src/common/state';
+
 import {searchSegment} from '../../base/binary_search';
 import {Actions} from '../../common/actions';
 import {PluginContext} from '../../common/plugin_api';
 import {NUM, STR} from '../../common/query_result';
 import {fromNs, toNs} from '../../common/time';
 import {TrackData} from '../../common/track_data';
+import {profileType} from '../../controller/flamegraph_controller';
 import {
   TrackController,
 } from '../../controller/track_controller';
@@ -30,7 +33,7 @@
 
 export interface Data extends TrackData {
   tsStarts: Float64Array;
-  types: string[];
+  types: ProfileType[];
 }
 
 export interface Config {
@@ -48,17 +51,22 @@
         resolution,
         length: 0,
         tsStarts: new Float64Array(),
-        types: new Array<string>(),
+        types: new Array<ProfileType>(),
       };
     }
     const queryRes = await this.query(`
-    select * from
-    (select distinct(ts) as ts, 'native' as type from heap_profile_allocation
-     where upid = ${this.config.upid}
-        union
-        select distinct(graph_sample_ts) as ts, 'graph' as type from
-        heap_graph_object
-        where upid = ${this.config.upid}) order by ts`);
+    select * from (
+      select distinct
+        ts,
+        'heap_profile:' ||
+          (select group_concat(distinct heap_name) from heap_profile_allocation
+            where upid = ${this.config.upid}) AS type
+      from heap_profile_allocation
+      where upid = ${this.config.upid}
+      union
+      select distinct graph_sample_ts as ts, 'graph' as type
+      from heap_graph_object
+      where upid = ${this.config.upid}) order by ts`);
     const numRows = queryRes.numRows();
     const data: Data = {
       start,
@@ -66,13 +74,13 @@
       resolution,
       length: numRows,
       tsStarts: new Float64Array(numRows),
-      types: new Array<string>(numRows),
+      types: new Array<ProfileType>(numRows),
     };
 
     const it = queryRes.iter({ts: NUM, type: STR});
     for (let row = 0; it.valid(); it.next(), row++) {
       data.tsStarts[row] = it.ts;
-      data.types[row] = it.type;
+      data.types[row] = profileType(it.type);
     }
     return data;
   }
diff --git a/ui/src/tracks/perf_samples_profile/index.ts b/ui/src/tracks/perf_samples_profile/index.ts
index 5e96f71..f3ddd74 100644
--- a/ui/src/tracks/perf_samples_profile/index.ts
+++ b/ui/src/tracks/perf_samples_profile/index.ts
@@ -16,6 +16,7 @@
 import {Actions} from '../../common/actions';
 import {PluginContext} from '../../common/plugin_api';
 import {NUM} from '../../common/query_result';
+import {ProfileType} from '../../common/state';
 import {fromNs, toNs} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {
@@ -165,8 +166,12 @@
 
     if (index !== -1) {
       const ts = data.tsStartsNs[index];
-      globals.makeSelection(Actions.selectPerfSamples(
-          {id: index, upid: this.config.upid, ts, type: 'perf'}));
+      globals.makeSelection(Actions.selectPerfSamples({
+        id: index,
+        upid: this.config.upid,
+        ts,
+        type: ProfileType.PERF_SAMPLE,
+      }));
       return true;
     }
     return false;