ui: Add callstack sampling flamegraph behind flag
Bug: 195934783
Change-Id: I7b1a38ea7fba379e189f1464f2ed16804033a354
diff --git a/src/trace_processor/dynamic/experimental_flamegraph_generator.cc b/src/trace_processor/dynamic/experimental_flamegraph_generator.cc
index 391888e..9af3e33 100644
--- a/src/trace_processor/dynamic/experimental_flamegraph_generator.cc
+++ b/src/trace_processor/dynamic/experimental_flamegraph_generator.cc
@@ -262,7 +262,7 @@
} else if (values.profile_type == "native") {
table = BuildNativeHeapProfileFlamegraph(context_->storage.get(),
values.upid, values.ts);
- } else if (values.profile_type == "callstack") {
+ } else if (values.profile_type == "perf") {
table = BuildNativeCallStackSamplingFlamegraph(context_->storage.get(),
values.upid, values.ts);
}
diff --git a/src/trace_processor/importers/proto/flamegraph_construction_algorithms.cc b/src/trace_processor/importers/proto/flamegraph_construction_algorithms.cc
index 2197bc9..8343a8e 100644
--- a/src/trace_processor/importers/proto/flamegraph_construction_algorithms.cc
+++ b/src/trace_processor/importers/proto/flamegraph_construction_algorithms.cc
@@ -178,11 +178,7 @@
BuildFlamegraphTableHeapSizeAndCount(
std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> tbl,
const std::vector<uint32_t>& callsite_to_merged_callsite,
- TraceStorage* storage,
const Table& filtered) {
- const tables::StackProfileCallsiteTable& callsites_tbl =
- storage->stack_profile_callsite_table();
-
for (auto it = filtered.IterateRows(); it; it.Next()) {
int64_t size =
it.Get(static_cast<uint32_t>(
@@ -200,8 +196,7 @@
PERFETTO_CHECK((size <= 0 && count <= 0) || (size >= 0 && count >= 0));
uint32_t merged_idx =
- callsite_to_merged_callsite[*callsites_tbl.id().IndexOf(
- CallsiteId(static_cast<uint32_t>(callsite_id)))];
+ callsite_to_merged_callsite[static_cast<unsigned long>(callsite_id)];
// On old heapprofd producers, the count field is incorrectly set and we
// zero it in proto_trace_parser.cc.
// As such, we cannot depend on count == 0 to imply size == 0, so we check
@@ -261,11 +256,7 @@
BuildFlamegraphTableCallstackSizeAndCount(
std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> tbl,
const std::vector<uint32_t>& callsite_to_merged_callsite,
- TraceStorage* storage,
const Table& filtered) {
- const tables::StackProfileCallsiteTable& callsites_tbl =
- storage->stack_profile_callsite_table();
-
for (auto it = filtered.IterateRows(); it; it.Next()) {
int64_t callsite_id =
it.Get(static_cast<uint32_t>(
@@ -273,8 +264,7 @@
.long_value;
uint32_t merged_idx =
- callsite_to_merged_callsite[*callsites_tbl.id().IndexOf(
- CallsiteId(static_cast<uint32_t>(callsite_id)))];
+ callsite_to_merged_callsite[static_cast<unsigned long>(callsite_id)];
tbl->mutable_size()->Set(merged_idx, tbl->size()[merged_idx] + 1);
tbl->mutable_count()->Set(merged_idx, tbl->count()[merged_idx] + 1);
}
@@ -320,7 +310,7 @@
filtered);
return BuildFlamegraphTableHeapSizeAndCount(
std::move(table_and_callsites.tbl),
- table_and_callsites.callsite_to_merged_callsite, storage, filtered);
+ table_and_callsites.callsite_to_merged_callsite, filtered);
}
std::unique_ptr<tables::ExperimentalFlamegraphNodesTable>
@@ -353,13 +343,13 @@
Table filtered_fully =
filtered_by_pid.Filter({storage->perf_sample_table().ts().le(timestamp)});
- StringId profile_type = storage->InternString("callstack");
+ StringId profile_type = storage->InternString("perf");
FlamegraphTableAndMergedCallsites table_and_callsites =
BuildFlamegraphTableTreeStructure(storage, upid, timestamp, profile_type,
filtered_fully);
return BuildFlamegraphTableCallstackSizeAndCount(
std::move(table_and_callsites.tbl),
- table_and_callsites.callsite_to_merged_callsite, storage, filtered_fully);
+ table_and_callsites.callsite_to_merged_callsite, filtered_fully);
}
} // namespace trace_processor
diff --git a/test/trace_processor/profiling/callstack_sampling_flamegraph.sql b/test/trace_processor/profiling/callstack_sampling_flamegraph.sql
index 44765c0..f4164b4 100644
--- a/test/trace_processor/profiling/callstack_sampling_flamegraph.sql
+++ b/test/trace_processor/profiling/callstack_sampling_flamegraph.sql
@@ -1 +1 @@
-select * from experimental_flamegraph(7689491063351, 30, 'callstack') limit 10;
+select * from experimental_flamegraph(7689491063351, 30, 'perf') limit 10;
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 37be6dc..a12390a 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -576,9 +576,13 @@
};
},
- selectHeapProfile(
- state: StateDraft,
- args: {id: number, upid: number, ts: number, type: string}): void {
+ selectHeapProfile(state: StateDraft, args: {
+ id: number,
+ upid: number,
+ ts: number,
+ type: string,
+ viewingOption?: HeapProfileFlamegraphViewingOption
+ }): void {
state.currentSelection = {
kind: 'HEAP_PROFILE',
id: args.id,
@@ -592,7 +596,7 @@
upid: args.upid,
ts: args.ts,
type: args.type,
- viewingOption: DEFAULT_VIEWING_OPTION,
+ viewingOption: args.viewingOption || DEFAULT_VIEWING_OPTION,
focusRegex: '',
};
},
diff --git a/ui/src/common/feature_flags.ts b/ui/src/common/feature_flags.ts
index 6106344..efd1ccf 100644
--- a/ui/src/common/feature_flags.ts
+++ b/ui/src/common/feature_flags.ts
@@ -46,8 +46,8 @@
function isFlagOverrides(o: object): o is FlagOverrides {
const states =
[OverrideState.TRUE.toString(), OverrideState.FALSE.toString()];
- for (const [k, v] of Object.entries(o)) {
- if (typeof k !== 'string' || typeof v !== 'string' || !states.includes(v)) {
+ for (const v of Object.values(o)) {
+ if (typeof v !== 'string' || !states.includes(v)) {
return false;
}
}
diff --git a/ui/src/common/flamegraph_util.ts b/ui/src/common/flamegraph_util.ts
index 2910952..c8070ff 100644
--- a/ui/src/common/flamegraph_util.ts
+++ b/ui/src/common/flamegraph_util.ts
@@ -12,12 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {CallsiteInfo} from '../common/state';
+import {CallsiteInfo} from './state';
export const SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY = 'SPACE';
export const ALLOC_SPACE_MEMORY_ALLOCATED_KEY = 'ALLOC_SPACE';
export const OBJECTS_ALLOCATED_NOT_FREED_KEY = 'OBJECTS';
export const OBJECTS_ALLOCATED_KEY = 'ALLOC_OBJECTS';
+export const PERF_SAMPLES_KEY = 'PERF_SAMPLES';
export const DEFAULT_VIEWING_OPTION = SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY;
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 7138d06..c73dd5c 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -77,7 +77,7 @@
}
export type HeapProfileFlamegraphViewingOption =
- 'SPACE'|'ALLOC_SPACE'|'OBJECTS'|'ALLOC_OBJECTS';
+ 'SPACE'|'ALLOC_SPACE'|'OBJECTS'|'ALLOC_OBJECTS'|'PERF_SAMPLES';
export interface CallsiteInfo {
id: number;
diff --git a/ui/src/controller/heap_profile_controller.ts b/ui/src/controller/heap_profile_controller.ts
index 8ec8e34..ef9df65 100644
--- a/ui/src/controller/heap_profile_controller.ts
+++ b/ui/src/controller/heap_profile_controller.ts
@@ -21,6 +21,7 @@
mergeCallsites,
OBJECTS_ALLOCATED_KEY,
OBJECTS_ALLOCATED_NOT_FREED_KEY,
+ PERF_SAMPLES_KEY,
SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY
} from '../common/flamegraph_util';
import {NUM, STR} from '../common/query_result';
@@ -236,13 +237,6 @@
// Alternatively consider collapsing frames of the same label.
const maxDepth = 100;
switch (viewingOption) {
- case SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY:
- orderBy = `where cumulative_size > 0 and depth < ${
- maxDepth} order by depth, parent_id,
- cumulative_size desc, name`;
- totalColumnName = 'cumulativeSize';
- selfColumnName = 'size';
- break;
case ALLOC_SPACE_MEMORY_ALLOCATED_KEY:
orderBy = `where cumulative_alloc_size > 0 and depth < ${
maxDepth} order by depth, parent_id,
@@ -264,6 +258,14 @@
totalColumnName = 'cumulativeAllocCount';
selfColumnName = 'count';
break;
+ case PERF_SAMPLES_KEY:
+ case SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY:
+ orderBy = `where cumulative_size > 0 and depth < ${
+ maxDepth} order by depth, parent_id,
+ cumulative_size desc, name`;
+ totalColumnName = 'cumulativeSize';
+ selfColumnName = 'size';
+ break;
default:
break;
}
@@ -285,7 +287,7 @@
IFNULL(line_number, -1) as lineNumber
from ${tableName} ${orderBy}`);
- const flamegraphData: CallsiteInfo[] = new Array();
+ const flamegraphData: CallsiteInfo[] = [];
const hashToindex: Map<number, number> = new Map();
const it = callsites.iter({
hash: NUM,
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index fefb2ae..bcb2b4c 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -21,6 +21,7 @@
import {TRACE_MARGIN_TIME_S} from '../common/constants';
import {Engine} from '../common/engine';
import {featureFlags, Flag} from '../common/feature_flags';
+import {PERF_SAMPLES_KEY} from '../common/flamegraph_util';
import {HttpRpcEngine} from '../common/http_rpc_engine';
import {NUM, NUM_NULL, QueryError, STR, STR_NULL} from '../common/query_result';
import {EngineMode} from '../common/state';
@@ -116,6 +117,12 @@
return [flag, m];
});
+const PERF_SAMPLE_FLAG = featureFlags.register({
+ id: 'perfSampleFlamegraph',
+ name: 'Perf Sample Flamegraph',
+ description: 'Show flamegraph generated by a perf sample.',
+ defaultValue: false
+});
// TraceController handles handshakes with the frontend for everything that
// concerns a single trace. It owns the WASM trace processor engine, handles
@@ -391,11 +398,29 @@
globals.dispatch(Actions.removeDebugTrack({}));
globals.dispatch(Actions.sortThreadTracks({}));
+
await this.selectFirstHeapProfile();
+ if (PERF_SAMPLE_FLAG.get()) {
+ await this.selectPerfSample();
+ }
return engineMode;
}
+ private async selectPerfSample() {
+ const query = `select ts, upid
+ from perf_sample
+ join thread using (utid)
+ order by ts desc limit 1`;
+ const profile = await assertExists(this.engine).query(query);
+ if (profile.numRows() !== 1) return;
+ const row = profile.firstRow({ts: NUM, upid: NUM});
+ const ts = row.ts;
+ const upid = row.upid;
+ globals.dispatch(Actions.selectHeapProfile(
+ {id: 0, upid, ts, type: 'perf', viewingOption: PERF_SAMPLES_KEY}));
+ }
+
private async selectFirstHeapProfile() {
const query = `select * from
(select distinct(ts) as ts, 'native' as type,
diff --git a/ui/src/frontend/heap_profile_panel.ts b/ui/src/frontend/heap_profile_panel.ts
index 7aadd2f..53b873e 100644
--- a/ui/src/frontend/heap_profile_panel.ts
+++ b/ui/src/frontend/heap_profile_panel.ts
@@ -14,11 +14,13 @@
import * as m from 'mithril';
+import {assertExists} from '../base/logging';
import {Actions} from '../common/actions';
import {
ALLOC_SPACE_MEMORY_ALLOCATED_KEY,
OBJECTS_ALLOCATED_KEY,
OBJECTS_ALLOCATED_NOT_FREED_KEY,
+ PERF_SAMPLES_KEY,
SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY,
} from '../common/flamegraph_util';
import {
@@ -42,6 +44,7 @@
enum ProfileType {
NATIVE_HEAP_PROFILE = 'native',
JAVA_HEAP_GRAPH = 'graph',
+ PERF_SAMPLE = 'perf'
}
function isProfileType(s: string): s is ProfileType {
@@ -169,6 +172,8 @@
return 'Heap Profile:';
case ProfileType.JAVA_HEAP_GRAPH:
return 'Java Heap:';
+ case ProfileType.PERF_SAMPLE:
+ return 'Perf sample:';
default:
throw new Error('unknown type');
}
@@ -181,14 +186,15 @@
const viewingOption =
globals.state.currentHeapProfileFlamegraph!.viewingOption;
switch (this.profileType) {
- case ProfileType.NATIVE_HEAP_PROFILE:
- return RENDER_SELF_AND_TOTAL;
case ProfileType.JAVA_HEAP_GRAPH:
if (viewingOption === OBJECTS_ALLOCATED_NOT_FREED_KEY) {
return RENDER_OBJ_COUNT;
} else {
return RENDER_SELF_AND_TOTAL;
}
+ case ProfileType.NATIVE_HEAP_PROFILE:
+ case ProfileType.PERF_SAMPLE:
+ return RENDER_SELF_AND_TOTAL;
default:
throw new Error('unknown type');
}
@@ -200,53 +206,11 @@
}));
}
- getButtonsClass(button: HeapProfileFlamegraphViewingOption): string {
- if (globals.state.currentHeapProfileFlamegraph === null) return '';
- return globals.state.currentHeapProfileFlamegraph.viewingOption === button ?
- '.chosen' :
- '';
- }
-
getViewingOptionButtons(): m.Children {
- const viewingOptions = [
- m(`button${this.getButtonsClass(SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY)}`,
- {
- onclick: () => {
- this.changeViewingOption(SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY);
- }
- },
- 'space'),
- m(`button${this.getButtonsClass(OBJECTS_ALLOCATED_NOT_FREED_KEY)}`,
- {
- onclick: () => {
- this.changeViewingOption(OBJECTS_ALLOCATED_NOT_FREED_KEY);
- }
- },
- 'objects'),
- ];
-
- if (this.profileType === ProfileType.NATIVE_HEAP_PROFILE) {
- viewingOptions.push(
- m(`button${this.getButtonsClass(ALLOC_SPACE_MEMORY_ALLOCATED_KEY)}`,
- {
- onclick: () => {
- this.changeViewingOption(ALLOC_SPACE_MEMORY_ALLOCATED_KEY);
- }
- },
- 'alloc space'),
- m(`button${this.getButtonsClass(OBJECTS_ALLOCATED_KEY)}`,
- {
- onclick: () => {
- this.changeViewingOption(OBJECTS_ALLOCATED_KEY);
- }
- },
- 'alloc objects'));
- }
- return m('div', ...viewingOptions);
- }
-
- changeViewingOption(viewingOption: HeapProfileFlamegraphViewingOption) {
- globals.dispatch(Actions.changeViewHeapProfileFlamegraph({viewingOption}));
+ return m(
+ 'div',
+ ...HeapProfileDetailsPanel.selectViewingOptions(
+ assertExists(this.profileType)));
}
downloadPprof() {
@@ -294,4 +258,47 @@
onMouseOut() {
this.flamegraph.onMouseOut();
}
+
+ private static selectViewingOptions(profileType: ProfileType) {
+ switch (profileType) {
+ case ProfileType.PERF_SAMPLE:
+ return [this.buildButtonComponent(PERF_SAMPLES_KEY, 'samples')];
+ case ProfileType.NATIVE_HEAP_PROFILE:
+ return [
+ this.buildButtonComponent(
+ SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, 'space'),
+ this.buildButtonComponent(OBJECTS_ALLOCATED_NOT_FREED_KEY, 'objects')
+ ];
+ case ProfileType.JAVA_HEAP_GRAPH:
+ return [
+ this.buildButtonComponent(
+ SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, 'space'),
+ this.buildButtonComponent(OBJECTS_ALLOCATED_NOT_FREED_KEY, 'objects'),
+ this.buildButtonComponent(
+ ALLOC_SPACE_MEMORY_ALLOCATED_KEY, 'alloc space'),
+ this.buildButtonComponent(OBJECTS_ALLOCATED_KEY, 'alloc objects')
+ ];
+ default:
+ throw new Error(`Unexpected profile type ${profileType}`);
+ }
+ }
+
+ private static buildButtonComponent(
+ viewingOption: HeapProfileFlamegraphViewingOption, text: string) {
+ const buttonsClass =
+ (globals.state.currentHeapProfileFlamegraph &&
+ globals.state.currentHeapProfileFlamegraph.viewingOption ===
+ viewingOption) ?
+ '.chosen' :
+ '';
+ return m(
+ `button${buttonsClass}`,
+ {
+ onclick: () => {
+ globals.dispatch(
+ Actions.changeViewHeapProfileFlamegraph({viewingOption}));
+ }
+ },
+ text);
+ }
}