tp+ui: resolve process/thread names for (is_pid)/(is_tid) args
Args produced from an (is_pid)/(is_tid) proto field annotation carried a
resolved upid/utid but were stored as a plain integer, so the UI showed a bare
number with no way to see or group by the process.
A upid/utid is an int with a semantic annotation, not a new primitive type. A
new Variadic type would touch every exhaustive Variadic::Type switch; instead a
side table records the annotation, keeping the args schema and value_type
untouched and leaving room for multiple annotations per key.
trace_processor:
- New __intrinsic_arg_annotation(key, annotation) table (SQL view
arg_annotation). An annotation is a property of the key, so each
(key, annotation) is stored once, not per arg set. AddUpid/AddUtid store
Variadic::Integer and tag the arg 'upid'/'utid'; the tag threads through
ArgsTracker / GlobalArgsTracker, which writes each distinct pair once.
- arg_set_to_json loads the (small) annotation table once and, next to a
<...>upid/<...>utid arg, emits a companion <...>process_name/<...>thread_name
resolved from the process/thread table at query time, so always the final
name. The details panel shows it with no UI-side work.
ui (datagrid): the slice-selection aggregator's args selector exposes the same
companion name key and resolves it by joining arg_annotation on key.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Change-Id: Ibd68b0625dfb88af307c52ca4a6785c99140d0a6
diff --git a/src/trace_processor/importers/common/args_tracker.cc b/src/trace_processor/importers/common/args_tracker.cc
index 9eeea75..8e17a18 100644
--- a/src/trace_processor/importers/common/args_tracker.cc
+++ b/src/trace_processor/importers/common/args_tracker.cc
@@ -107,7 +107,8 @@
ArgsInserter& ArgsInserter::AddArg(StringId flat_key,
StringId key,
Variadic value,
- UpdatePolicy update_policy) {
+ UpdatePolicy update_policy,
+ StringId annotation) {
std::vector<CompactArg>& args = buffer_->args;
base::FlatHashMap<StringId, uint32_t>& key_index = buffer_->key_index;
@@ -138,11 +139,13 @@
existing->flat_key = flat_key;
existing->value = value;
existing->update_policy = update_policy;
+ existing->annotation = annotation;
return *this;
}
auto new_index = static_cast<uint32_t>(args.size());
- args.emplace_back(CompactArg{flat_key, key, value, update_policy});
+ args.emplace_back(
+ CompactArg{flat_key, key, value, update_policy, annotation});
if (key_index.size() != 0) {
key_index.Insert(key, new_index);
} else if (args.size() >= kKeyIndexThreshold) {
diff --git a/src/trace_processor/importers/common/args_tracker.h b/src/trace_processor/importers/common/args_tracker.h
index b97dc4f..57448a4 100644
--- a/src/trace_processor/importers/common/args_tracker.h
+++ b/src/trace_processor/importers/common/args_tracker.h
@@ -81,7 +81,14 @@
ArgsInserter& AddArg(StringId flat_key,
StringId key,
Variadic v,
- UpdatePolicy update_policy = UpdatePolicy::kAddOrUpdate);
+ UpdatePolicy update_policy = UpdatePolicy::kAddOrUpdate,
+ StringId annotation = kNullStringId);
+ ArgsInserter& AddArg(StringId flat_key,
+ StringId key,
+ Variadic v,
+ StringId annotation) {
+ return AddArg(flat_key, key, v, UpdatePolicy::kAddOrUpdate, annotation);
+ }
// IncrementArrayEntryIndex() and GetNextArrayEntryIndex() provide a way to
// track the next array index for an array under a specific key.
diff --git a/src/trace_processor/importers/common/global_args_tracker.h b/src/trace_processor/importers/common/global_args_tracker.h
index 86968f9..5b5e5bc 100644
--- a/src/trace_processor/importers/common/global_args_tracker.h
+++ b/src/trace_processor/importers/common/global_args_tracker.h
@@ -52,6 +52,7 @@
StringId key = kNullStringId;
Variadic value = Variadic::Integer(0);
UpdatePolicy update_policy = UpdatePolicy::kAddOrUpdate;
+ StringId annotation = kNullStringId;
};
static_assert(std::is_trivially_destructible<CompactArg>::value,
"Args must be trivially destructible");
@@ -218,6 +219,18 @@
}
row.value_type = storage_->GetIdForVariadicType(arg.value.type);
arg_table.Insert(row);
+ // An annotation is a property of the key, so record each (key,
+ // annotation) once rather than per arg set.
+ if (!arg.annotation.is_null()) {
+ uint64_t dedup = (static_cast<uint64_t>(arg.key.raw_id()) << 32) |
+ arg.annotation.raw_id();
+ if (seen_annotations_.Insert(dedup, true).second) {
+ tables::ArgAnnotationTable::Row annotation;
+ annotation.key = arg.key;
+ annotation.annotation = arg.annotation;
+ storage_->mutable_arg_annotation_table()->Insert(annotation);
+ }
+ }
}
return arg_set_id;
}
@@ -225,6 +238,9 @@
base::FlatHashMap<ArgSetHash, uint32_t, base::AlreadyHashed<ArgSetHash>>
arg_row_for_hash_;
+ // (key << 32 | annotation) StringIds already written to arg_annotation.
+ base::FlatHashMap<uint64_t, bool> seen_annotations_;
+
// Pool of ArgsInserter buffers: `buffer_storage_` owns them, `free_buffers_`
// lists those available for reuse.
std::vector<std::unique_ptr<ArgsBuffer>> buffer_storage_;
diff --git a/src/trace_processor/importers/proto/args_parser.cc b/src/trace_processor/importers/proto/args_parser.cc
index 58a3866..355f93c 100644
--- a/src/trace_processor/importers/proto/args_parser.cc
+++ b/src/trace_processor/importers/proto/args_parser.cc
@@ -91,13 +91,15 @@
void ArgsParser::AddUpid(Id flat_key, Id key, int64_t pid) {
if (auto upid = process_tracker_.GetProcessOrNull(pid)) {
- inserter_.AddArg(flat_key, key, Variadic::Integer(*upid));
+ inserter_.AddArg(flat_key, key, Variadic::Integer(*upid),
+ storage_.InternString("upid"));
}
}
void ArgsParser::AddUtid(Id flat_key, Id key, int64_t tid) {
if (auto utid = process_tracker_.GetThreadOrNull(tid)) {
- inserter_.AddArg(flat_key, key, Variadic::Integer(*utid));
+ inserter_.AddArg(flat_key, key, Variadic::Integer(*utid),
+ storage_.InternString("utid"));
}
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql b/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql
index 21fc526..7a61f6f 100644
--- a/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql
@@ -270,6 +270,16 @@
END AS display_value
FROM __intrinsic_args;
+-- Semantic annotations on args, joined to `args` by (arg_set_id, key).
+CREATE PERFETTO VIEW arg_annotation(
+ -- The key of the annotated arg.
+ key STRING,
+ -- The annotation (e.g. 'upid', 'utid').
+ annotation STRING
+)
+AS
+SELECT key, annotation FROM __intrinsic_arg_annotation;
+
-- Contains all Linux perf sessions in the trace, including sessions which
-- only recorded counters and did not capture any callstacks.
CREATE PERFETTO VIEW perf_session(
diff --git a/src/trace_processor/plugins/args/args.cc b/src/trace_processor/plugins/args/args.cc
index 6d48201..2528eff 100644
--- a/src/trace_processor/plugins/args/args.cc
+++ b/src/trace_processor/plugins/args/args.cc
@@ -24,6 +24,7 @@
#include <vector>
#include "perfetto/base/compiler.h"
+#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/string_utils.h"
#include "src/trace_processor/containers/null_term_string_view.h"
#include "src/trace_processor/core/plugin/plugin.h"
@@ -220,13 +221,46 @@
args_cursor.SetFilterValueUnchecked(0, arg_set_id);
args_cursor.Execute();
+ StringId upid_ann = storage->InternString("upid");
+ StringId utid_ann = storage->InternString("utid");
+ // arg_annotation is keyed by arg key, so load it once and reuse.
+ auto& annotation_by_key = user_data->annotation_by_key;
+ if (!user_data->annotations_loaded) {
+ auto& annotation_cursor = user_data->annotation_cursor;
+ annotation_cursor.Execute();
+ for (; !annotation_cursor.Eof(); annotation_cursor.Next()) {
+ annotation_by_key.Insert(annotation_cursor.key(),
+ annotation_cursor.annotation());
+ }
+ user_data->annotations_loaded = true;
+ }
+
// Reuse arg_set - clear but retain capacity
arg_set.Clear();
for (; !args_cursor.Eof(); args_cursor.Next()) {
- const auto result = arg_set.AppendArg(storage->GetString(args_cursor.key()),
- GetArgValue(*storage, args_cursor));
- if (!result.ok()) {
- return sqlite::result::Error(ctx, result.c_message());
+ NullTermStringView key = storage->GetString(args_cursor.key());
+ Variadic v = GetArgValue(*storage, args_cursor);
+ if (auto r = arg_set.AppendArg(key, v); !r.ok()) {
+ return sqlite::result::Error(ctx, r.c_message());
+ }
+ // A upid/utid arg also emits a companion process/thread name.
+ const StringId* ann = annotation_by_key.Find(args_cursor.key());
+ if (ann != nullptr && (*ann == upid_ann || *ann == utid_ann)) {
+ bool is_upid = *ann == upid_ann;
+ auto id = static_cast<uint32_t>(v.int_value);
+ StringId name =
+ is_upid ? storage->process_table()[id].name().value_or(kNullStringId)
+ : storage->thread_table()[id].name().value_or(kNullStringId);
+ if (!name.is_null()) {
+ std::string k = key.ToStdString();
+ std::string name_key = k.substr(0, k.size() - 4) +
+ (is_upid ? "process_name" : "thread_name");
+ if (auto r = arg_set.AppendArg(NullTermStringView(name_key),
+ Variadic::String(name));
+ !r.ok()) {
+ return sqlite::result::Error(ctx, r.c_message());
+ }
+ }
}
}
diff --git a/src/trace_processor/plugins/args/args.h b/src/trace_processor/plugins/args/args.h
index d91da30..060b447 100644
--- a/src/trace_processor/plugins/args/args.h
+++ b/src/trace_processor/plugins/args/args.h
@@ -17,6 +17,7 @@
#include <optional>
+#include "perfetto/ext/base/flat_hash_map.h"
#include "src/trace_processor/core/dataframe/specs.h"
#include "src/trace_processor/sqlite/bindings/sqlite_function.h"
#include "src/trace_processor/sqlite/bindings/sqlite_value.h"
@@ -58,10 +59,15 @@
0,
dataframe::Eq{},
std::nullopt,
- }})) {}
+ }})),
+ annotation_cursor(s->arg_annotation_table().CreateCursor()) {}
TraceStorage* storage;
tables::ArgTable::ConstCursor arg_cursor;
+ tables::ArgAnnotationTable::ConstCursor annotation_cursor;
+ // key -> annotation, loaded once from arg_annotation on first use.
+ base::FlatHashMap<StringId, StringId> annotation_by_key;
+ bool annotations_loaded = false;
json::JsonSerializer json_serializer;
ArgSet arg_set;
};
diff --git a/src/trace_processor/plugins/storage_tables/storage_tables.cc b/src/trace_processor/plugins/storage_tables/storage_tables.cc
index 85d0fd5..c03d62b 100644
--- a/src/trace_processor/plugins/storage_tables/storage_tables.cc
+++ b/src/trace_processor/plugins/storage_tables/storage_tables.cc
@@ -183,6 +183,7 @@
AddDataframe(out, s->mutable_memory_snapshot_node_table());
AddDataframe(out, s->mutable_experimental_proto_path_table());
AddDataframe(out, s->mutable_arg_table());
+ AddDataframe(out, s->mutable_arg_annotation_table());
AddDataframe(out, s->mutable_heap_graph_object_table());
AddDataframe(out, s->mutable_heap_graph_primitive_table());
AddDataframe(out, s->mutable_heap_graph_object_data_table());
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index dc64954..9adc489 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -444,6 +444,12 @@
tables::ArgTable* mutable_arg_table() {
return mutable_table<tables::ArgTable>();
}
+ const tables::ArgAnnotationTable& arg_annotation_table() const {
+ return table<tables::ArgAnnotationTable>();
+ }
+ tables::ArgAnnotationTable* mutable_arg_annotation_table() {
+ return mutable_table<tables::ArgAnnotationTable>();
+ }
const tables::ChromeRawTable& chrome_raw_table() const {
return table<tables::ChromeRawTable>();
diff --git a/src/trace_processor/tables/metadata_tables.py b/src/trace_processor/tables/metadata_tables.py
index d3b510d..bf7cbbf 100644
--- a/src/trace_processor/tables/metadata_tables.py
+++ b/src/trace_processor/tables/metadata_tables.py
@@ -657,6 +657,36 @@
''',
}))
+ARG_ANNOTATION_TABLE = Table(
+ python_module=__file__,
+ class_name='ArgAnnotationTable',
+ sql_name='__intrinsic_arg_annotation',
+ columns=[
+ C(
+ 'key',
+ CppString(),
+ cpp_access=CppAccess.READ,
+ cpp_access_duration=CppAccessDuration.POST_FINALIZATION,
+ ),
+ C(
+ 'annotation',
+ CppString(),
+ cpp_access=CppAccess.READ,
+ cpp_access_duration=CppAccessDuration.POST_FINALIZATION,
+ ),
+ ],
+ wrapping_sql_view=WrappingSqlView(
+ view_name='__intrinsic_arg_annotation_view'),
+ tabledoc=TableDoc(
+ doc="""Semantic annotations on arg keys, joined to args by key. An
+ annotation is a property of the key, so it is stored once no matter how
+ many arg sets use the key. A key may have several (e.g. 'upid').""",
+ group='Misc',
+ columns={
+ 'key': 'The key of the annotated arg.',
+ 'annotation': "The annotation (e.g. 'upid', 'utid').",
+ }))
+
ARG_TABLE = Table(
python_module=__file__,
class_name='ArgTable',
@@ -1139,6 +1169,7 @@
# Keep this list sorted.
ALL_TABLES = [
ARG_TABLE,
+ ARG_ANNOTATION_TABLE,
BUILD_FLAGS_TABLE,
CHROME_RAW_TABLE,
CLOCK_SNAPSHOT_TABLE,
diff --git a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/thread_slice_aggregator.ts b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/thread_slice_aggregator.ts
index 4159399..bf35488 100644
--- a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/thread_slice_aggregator.ts
+++ b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/thread_slice_aggregator.ts
@@ -447,15 +447,49 @@
},
},
args: {
- expression: (alias, key) =>
- `extract_arg(${alias}.arg_set_id, '${key}')`,
+ expression: (alias, key = '') => {
+ const set = `${alias}.arg_set_id`;
+ // An (is_pid)/(is_tid) arg exposes a companion *_process_name /
+ // *_thread_name key resolved from the annotated upid/utid.
+ for (const [vt, table, suffix] of [
+ ['upid', 'process', 'process_name'],
+ ['utid', 'thread', 'thread_name'],
+ ] as const) {
+ if (key.endsWith(suffix)) {
+ const idKey = key.slice(0, -suffix.length) + vt;
+ return (
+ `COALESCE((SELECT r.name FROM ${table} r JOIN args a` +
+ ` ON a.arg_set_id = ${set} AND a.key = '${idKey}'` +
+ ` JOIN arg_annotation an` +
+ ` ON an.key = a.key AND an.annotation = '${vt}'` +
+ ` WHERE r.${vt} = a.int_value),` +
+ ` extract_arg(${set}, '${key}'))`
+ );
+ }
+ }
+ return `extract_arg(${set}, '${key}')`;
+ },
parameterized: true,
parameterKeysQuery: (tableOrSubquery, alias) => `
- SELECT DISTINCT args.key
- FROM (${tableOrSubquery}) AS ${alias}
- JOIN args ON args.arg_set_id = ${alias}.arg_set_id
- WHERE args.key IS NOT NULL
- ORDER BY args.key
+ SELECT DISTINCT key FROM (
+ SELECT args.key AS key
+ FROM (${tableOrSubquery}) AS ${alias}
+ JOIN args ON args.arg_set_id = ${alias}.arg_set_id
+ WHERE args.key IS NOT NULL
+ UNION ALL
+ SELECT CASE an.annotation
+ WHEN 'upid'
+ THEN substr(an.key, 1, length(an.key) - 4) || 'process_name'
+ WHEN 'utid'
+ THEN substr(an.key, 1, length(an.key) - 4) || 'thread_name'
+ END AS key
+ FROM (${tableOrSubquery}) AS ${alias}
+ JOIN args ON args.arg_set_id = ${alias}.arg_set_id
+ JOIN arg_annotation an ON an.key = args.key
+ WHERE an.annotation IN ('upid', 'utid')
+ )
+ WHERE key IS NOT NULL
+ ORDER BY key
LIMIT 1000
`,
},