Merge "Trace Redaction - Convert task rename from primitive to filter" into main
diff --git a/Android.bp b/Android.bp
index dcc140c..65a914b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2377,7 +2377,6 @@
":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions",
":perfetto_src_trace_processor_sorter_sorter",
":perfetto_src_trace_processor_sqlite_bindings_bindings",
- ":perfetto_src_trace_processor_sqlite_query_constraints",
":perfetto_src_trace_processor_sqlite_sqlite",
":perfetto_src_trace_processor_storage_minimal",
":perfetto_src_trace_processor_storage_storage",
@@ -12365,6 +12364,7 @@
"src/trace_processor/perfetto_sql/stdlib/android/binder.sql",
"src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql",
"src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/device.sql",
"src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
"src/trace_processor/perfetto_sql/stdlib/android/frames/per_frame_metrics.sql",
"src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql",
@@ -12376,6 +12376,7 @@
"src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql",
"src/trace_processor/perfetto_sql/stdlib/android/network_packets.sql",
"src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql",
"src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql",
"src/trace_processor/perfetto_sql/stdlib/android/screenshots.sql",
"src/trace_processor/perfetto_sql/stdlib/android/services.sql",
@@ -12399,6 +12400,8 @@
"src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql",
"src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql",
"src/trace_processor/perfetto_sql/stdlib/cpu/cpus.sql",
+ "src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql",
+ "src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql",
"src/trace_processor/perfetto_sql/stdlib/cpu/size.sql",
"src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/args.sql",
"src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/counters.sql",
@@ -12437,6 +12440,10 @@
"src/trace_processor/perfetto_sql/stdlib/stack_trace/jit.sql",
"src/trace_processor/perfetto_sql/stdlib/time/conversion.sql",
"src/trace_processor/perfetto_sql/stdlib/v8/jit.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql",
],
cmd: "$(location tools/gen_amalgamated_sql.py) --namespace=stdlib --cpp-out=$(out) $(in)",
out: [
@@ -12503,14 +12510,6 @@
name: "perfetto_src_trace_processor_sqlite_bindings_bindings",
}
-// GN: //src/trace_processor/sqlite:query_constraints
-filegroup {
- name: "perfetto_src_trace_processor_sqlite_query_constraints",
- srcs: [
- "src/trace_processor/sqlite/query_constraints.cc",
- ],
-}
-
// GN: //src/trace_processor/sqlite:sqlite
filegroup {
name: "perfetto_src_trace_processor_sqlite_sqlite",
@@ -12519,7 +12518,6 @@
"src/trace_processor/sqlite/sql_source.cc",
"src/trace_processor/sqlite/sql_stats_table.cc",
"src/trace_processor/sqlite/sqlite_engine.cc",
- "src/trace_processor/sqlite/sqlite_table.cc",
"src/trace_processor/sqlite/sqlite_tokenizer.cc",
"src/trace_processor/sqlite/sqlite_utils.cc",
"src/trace_processor/sqlite/stats_table.cc",
@@ -12531,7 +12529,6 @@
name: "perfetto_src_trace_processor_sqlite_unittests",
srcs: [
"src/trace_processor/sqlite/db_sqlite_table_unittest.cc",
- "src/trace_processor/sqlite/query_constraints_unittest.cc",
"src/trace_processor/sqlite/sql_source_unittest.cc",
"src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc",
"src/trace_processor/sqlite/sqlite_utils_unittest.cc",
@@ -12851,6 +12848,7 @@
srcs: [
"src/trace_redaction/build_timeline.cc",
"src/trace_redaction/filter_ftrace_using_allowlist.cc",
+ "src/trace_redaction/filter_print_events.cc",
"src/trace_redaction/filter_sched_waking_events.cc",
"src/trace_redaction/filter_task_rename.cc",
"src/trace_redaction/find_package_uid.cc",
@@ -12861,6 +12859,7 @@
"src/trace_redaction/prune_package_list.cc",
"src/trace_redaction/redact_sched_switch.cc",
"src/trace_redaction/scrub_ftrace_events.cc",
+ "src/trace_redaction/scrub_process_stats.cc",
"src/trace_redaction/scrub_process_trees.cc",
"src/trace_redaction/scrub_trace_packet.cc",
"src/trace_redaction/trace_redaction_framework.cc",
@@ -14351,7 +14350,6 @@
":perfetto_src_trace_processor_sorter_sorter",
":perfetto_src_trace_processor_sorter_unittests",
":perfetto_src_trace_processor_sqlite_bindings_bindings",
- ":perfetto_src_trace_processor_sqlite_query_constraints",
":perfetto_src_trace_processor_sqlite_sqlite",
":perfetto_src_trace_processor_sqlite_unittests",
":perfetto_src_trace_processor_storage_minimal",
@@ -15067,7 +15065,6 @@
":perfetto_src_trace_processor_rpc_stdiod",
":perfetto_src_trace_processor_sorter_sorter",
":perfetto_src_trace_processor_sqlite_bindings_bindings",
- ":perfetto_src_trace_processor_sqlite_query_constraints",
":perfetto_src_trace_processor_sqlite_sqlite",
":perfetto_src_trace_processor_storage_minimal",
":perfetto_src_trace_processor_storage_storage",
@@ -15437,7 +15434,6 @@
":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions",
":perfetto_src_trace_processor_sorter_sorter",
":perfetto_src_trace_processor_sqlite_bindings_bindings",
- ":perfetto_src_trace_processor_sqlite_query_constraints",
":perfetto_src_trace_processor_sqlite_sqlite",
":perfetto_src_trace_processor_storage_minimal",
":perfetto_src_trace_processor_storage_storage",
diff --git a/BUILD b/BUILD
index 4b1e498..032036c 100644
--- a/BUILD
+++ b/BUILD
@@ -261,7 +261,6 @@
":src_trace_processor_rpc_rpc",
":src_trace_processor_sorter_sorter",
":src_trace_processor_sqlite_bindings_bindings",
- ":src_trace_processor_sqlite_query_constraints",
":src_trace_processor_sqlite_sqlite",
":src_trace_processor_storage_minimal",
":src_trace_processor_storage_storage",
@@ -2415,6 +2414,7 @@
"src/trace_processor/perfetto_sql/stdlib/android/binder.sql",
"src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql",
"src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/device.sql",
"src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
"src/trace_processor/perfetto_sql/stdlib/android/freezer.sql",
"src/trace_processor/perfetto_sql/stdlib/android/garbage_collection.sql",
@@ -2424,6 +2424,7 @@
"src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql",
"src/trace_processor/perfetto_sql/stdlib/android/network_packets.sql",
"src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql",
"src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql",
"src/trace_processor/perfetto_sql/stdlib/android/screenshots.sql",
"src/trace_processor/perfetto_sql/stdlib/android/services.sql",
@@ -2468,6 +2469,8 @@
name = "src_trace_processor_perfetto_sql_stdlib_cpu_cpu",
srcs = [
"src/trace_processor/perfetto_sql/stdlib/cpu/cpus.sql",
+ "src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql",
+ "src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql",
"src/trace_processor/perfetto_sql/stdlib/cpu/size.sql",
],
)
@@ -2600,6 +2603,17 @@
],
)
+# GN target: //src/trace_processor/perfetto_sql/stdlib/wattson:wattson
+perfetto_filegroup(
+ name = "src_trace_processor_perfetto_sql_stdlib_wattson_wattson",
+ srcs = [
+ "src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql",
+ ],
+)
+
# GN target: //src/trace_processor/perfetto_sql/stdlib:stdlib
perfetto_cc_amalgamated_sql(
name = "src_trace_processor_perfetto_sql_stdlib_stdlib",
@@ -2624,6 +2638,7 @@
":src_trace_processor_perfetto_sql_stdlib_stack_trace_stack_trace",
":src_trace_processor_perfetto_sql_stdlib_time_time",
":src_trace_processor_perfetto_sql_stdlib_v8_v8",
+ ":src_trace_processor_perfetto_sql_stdlib_wattson_wattson",
],
outs = [
"src/trace_processor/perfetto_sql/stdlib/stdlib.h",
@@ -2681,15 +2696,6 @@
],
)
-# GN target: //src/trace_processor/sqlite:query_constraints
-perfetto_filegroup(
- name = "src_trace_processor_sqlite_query_constraints",
- srcs = [
- "src/trace_processor/sqlite/query_constraints.cc",
- "src/trace_processor/sqlite/query_constraints.h",
- ],
-)
-
# GN target: //src/trace_processor/sqlite:sqlite
perfetto_filegroup(
name = "src_trace_processor_sqlite_sqlite",
@@ -2697,7 +2703,6 @@
"src/trace_processor/sqlite/db_sqlite_table.cc",
"src/trace_processor/sqlite/db_sqlite_table.h",
"src/trace_processor/sqlite/module_lifecycle_manager.h",
- "src/trace_processor/sqlite/query_cache.h",
"src/trace_processor/sqlite/scoped_db.h",
"src/trace_processor/sqlite/sql_source.cc",
"src/trace_processor/sqlite/sql_source.h",
@@ -2705,8 +2710,6 @@
"src/trace_processor/sqlite/sql_stats_table.h",
"src/trace_processor/sqlite/sqlite_engine.cc",
"src/trace_processor/sqlite/sqlite_engine.h",
- "src/trace_processor/sqlite/sqlite_table.cc",
- "src/trace_processor/sqlite/sqlite_table.h",
"src/trace_processor/sqlite/sqlite_tokenizer.cc",
"src/trace_processor/sqlite/sqlite_tokenizer.h",
"src/trace_processor/sqlite/sqlite_utils.cc",
@@ -5734,7 +5737,6 @@
":src_trace_processor_perfetto_sql_intrinsics_table_functions_tables",
":src_trace_processor_sorter_sorter",
":src_trace_processor_sqlite_bindings_bindings",
- ":src_trace_processor_sqlite_query_constraints",
":src_trace_processor_sqlite_sqlite",
":src_trace_processor_storage_minimal",
":src_trace_processor_storage_storage",
@@ -5907,7 +5909,6 @@
":src_trace_processor_rpc_stdiod",
":src_trace_processor_sorter_sorter",
":src_trace_processor_sqlite_bindings_bindings",
- ":src_trace_processor_sqlite_query_constraints",
":src_trace_processor_sqlite_sqlite",
":src_trace_processor_storage_minimal",
":src_trace_processor_storage_storage",
@@ -6132,7 +6133,6 @@
":src_trace_processor_perfetto_sql_intrinsics_table_functions_tables",
":src_trace_processor_sorter_sorter",
":src_trace_processor_sqlite_bindings_bindings",
- ":src_trace_processor_sqlite_query_constraints",
":src_trace_processor_sqlite_sqlite",
":src_trace_processor_storage_minimal",
":src_trace_processor_storage_storage",
diff --git a/CHANGELOG b/CHANGELOG
index abdf0f9..788d61f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,16 @@
Trace Processor:
*
UI:
+ *
+ SDK:
+ *
+
+
+v44.0 - 2024-04-10:
+ Trace Processor:
+ * New modules added to standard library: `frames.timeline`,
+ `frame.per_frame_metrics`, `sched.time_in_state`.
+ UI:
* Per-cpu scheduling tracks now distinguish real-time priority threads with
a hatched pattern and name prefix. Based on priority during switch-in.
* Added ftrace event cropping for traces recorded by perfetto v44+.
@@ -11,6 +21,19 @@
per-cpu data streams. This should significantly improve the presentation
of RING_BUFFER traces, removing artifacts such as never-ending slices
starting at the beginning of the trace.
+ * Significantly improved trace load times.
+ * Improved counter track view modes, including log scale, expanded view, and
+ the ability for plugin authors to link scales of several counter tracks
+ together.
+ * Add dominated size and objects to Java heap graph view.
+ Added hotkey Q to open and close bottom drawer.
+ * Fixed bug where timeline header and tracks could become horizontally
+ misaligned when using browser zoom.
+ * Fixed crash when hot-reloading Sass during development.
+ * Fixed bug where crashed debug tracks could not be closed.
+ * Fixed missing flame graph details for area selections.
+ * Consistent reporting of durations for incomplete slices.
+ * Switch to using prettier for formatting TS & Sass instead of ESLint.
SDK:
* "track_event" categories are disabled by default in the C API, if they
don't match anything in the data source config. This behavior differs from
diff --git a/docs/case-studies/memory.md b/docs/case-studies/memory.md
index b55bdce..93f67a2 100644
--- a/docs/case-studies/memory.md
+++ b/docs/case-studies/memory.md
@@ -325,7 +325,7 @@
Use the `tools/heap_profile` script to profile a process. If you are having
trouble make sure you are using the [latest version](
-https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile).
+https://raw.githubusercontent.com/google/perfetto/main/tools/heap_profile).
See all the arguments using `tools/heap_profile -h`, or use the defaults
and just profile a process (e.g. `system_server`):
@@ -390,7 +390,7 @@
We can get a snapshot of the graph of all the Java objects that constitute the
Java heap. We use the `tools/java_heap_dump` script. If you are having trouble
make sure you are using the [latest version](
-https://raw.githubusercontent.com/google/perfetto/master/tools/java_heap_dump).
+https://raw.githubusercontent.com/google/perfetto/main/tools/java_heap_dump).
```bash
$ tools/java_heap_dump -n com.android.systemui
diff --git a/docs/faq.md b/docs/faq.md
index 89491ec..02266ec 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -8,7 +8,7 @@
This can be used as follows:
```sh
-curl -OL https://github.com/google/perfetto/raw/master/tools/open_trace_in_ui
+curl -OL https://github.com/google/perfetto/raw/main/tools/open_trace_in_ui
chmod +x open_trace_in_ui
./open_trace_in_ui -i /path/to/trace
```
diff --git a/docs/instrumentation/heapprofd-api.md b/docs/instrumentation/heapprofd-api.md
index 0ef3f90..279f3d5 100644
--- a/docs/instrumentation/heapprofd-api.md
+++ b/docs/instrumentation/heapprofd-api.md
@@ -100,11 +100,11 @@
## Profile your App
Then, use the [heap_profile](
-https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile)
+https://raw.githubusercontent.com/google/perfetto/main/tools/heap_profile)
script to get a profile to generate textpb of the config.
To convert to a binary proto, you additionally need to download
[`perfetto_trace.proto`](
-https://raw.githubusercontent.com/google/perfetto/master/protos/perfetto/trace/perfetto_trace.proto)
+https://raw.githubusercontent.com/google/perfetto/main/protos/perfetto/trace/perfetto_trace.proto)
and have recent version of the protoc compiler installed.
[Learn how to install protoc](https://grpc.io/docs/protoc-installation).
diff --git a/docs/quickstart/android-tracing.md b/docs/quickstart/android-tracing.md
index 3a0d61a..6e8e9cb 100644
--- a/docs/quickstart/android-tracing.md
+++ b/docs/quickstart/android-tracing.md
@@ -80,7 +80,7 @@
On Linux and Mac:
```bash
-curl -O https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace
+curl -O https://raw.githubusercontent.com/google/perfetto/main/tools/record_android_trace
chmod u+x record_android_trace
# See ./record_android_trace --help for more
@@ -91,7 +91,7 @@
On Windows:
```bash
-curl -O https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace
+curl -O https://raw.githubusercontent.com/google/perfetto/main/tools/record_android_trace
python3 record_android_trace -o trace_file.perfetto-trace -t 30s -b 64mb \
sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory
```
diff --git a/docs/quickstart/callstack-sampling.md b/docs/quickstart/callstack-sampling.md
index 3741c1a..30fd74a 100644
--- a/docs/quickstart/callstack-sampling.md
+++ b/docs/quickstart/callstack-sampling.md
@@ -29,7 +29,7 @@
Download `cpu_profile` (if you don't have a Perfetto checkout):
```bash
-curl -LO https://raw.githubusercontent.com/google/perfetto/master/tools/cpu_profile
+curl -LO https://raw.githubusercontent.com/google/perfetto/main/tools/cpu_profile
chmod +x cpu_profile
```
diff --git a/docs/quickstart/heap-profiling.md b/docs/quickstart/heap-profiling.md
index 3a58f20..114f5e6 100644
--- a/docs/quickstart/heap-profiling.md
+++ b/docs/quickstart/heap-profiling.md
@@ -29,7 +29,7 @@
Download the `tools/heap_profile` (if you don't have a perfetto checkout):
```bash
-curl -LO https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile
+curl -LO https://raw.githubusercontent.com/google/perfetto/main/tools/heap_profile
chmod +x heap_profile
```
@@ -56,7 +56,7 @@
```
Download the
-[heap_profile](https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile)
+[heap_profile](https://raw.githubusercontent.com/google/perfetto/main/tools/heap_profile)
script. Then start the profile:
```bash
diff --git a/gn/write_buildflag_header.py b/gn/write_buildflag_header.py
index 30751a5..3f11a2f 100644
--- a/gn/write_buildflag_header.py
+++ b/gn/write_buildflag_header.py
@@ -82,7 +82,7 @@
lines.append('#endif // %s' % guard)
lines.append('')
- with open(args.out, 'w') as out:
+ with open(args.out, 'w', newline='\n') as out:
out.write(COPYRIGHT_HEADER)
out.write('\n'.join(lines))
diff --git a/include/perfetto/trace_processor/trace_processor.h b/include/perfetto/trace_processor/trace_processor.h
index f50a7c8..e46d1a7 100644
--- a/include/perfetto/trace_processor/trace_processor.h
+++ b/include/perfetto/trace_processor/trace_processor.h
@@ -140,9 +140,6 @@
virtual std::vector<uint8_t> GetMetricDescriptors() = 0;
};
-// When set, logs SQLite actions on the console.
-void PERFETTO_EXPORT_COMPONENT EnableSQLiteVtableDebugging();
-
} // namespace trace_processor
} // namespace perfetto
diff --git a/protos/perfetto/trace/android/surfaceflinger_layers.proto b/protos/perfetto/trace/android/surfaceflinger_layers.proto
index d85dc7f..6640fa7 100644
--- a/protos/perfetto/trace/android/surfaceflinger_layers.proto
+++ b/protos/perfetto/trace/android/surfaceflinger_layers.proto
@@ -51,12 +51,15 @@
// elapsed realtime in nanos since boot of when this entry was logged
optional sfixed64 elapsed_realtime_nanos = 1;
- // where the trace originated
+ // SurfaceFlinger's stage where the snapshot was triggered.
+ // Currently either "visibleRegionsDirty" or "bufferLatched".
optional string where = 2;
optional LayersProto layers = 3;
// Blob for the current HWC information for all layers, reported by dumpsys.
+ // Example:
+ // "maxDownScale: 4, maxFullWidth: 8192, HWState: 1, AssignedState: 3, ..."
optional string hwc_blob = 4;
// Excludes state sent during composition like visible region and composition
@@ -78,6 +81,7 @@
message DisplayProto {
optional uint64 id = 1;
+ // Display descriptor, e.g. "Built-In Screen"
optional string name = 2;
optional uint32 layer_stack = 3;
optional SizeProto size = 4;
@@ -112,12 +116,14 @@
// unique id per layer.
optional int32 id = 1;
// unique name per layer.
+ // Example: "Wallpaper".
optional string name = 2;
// list of children this layer may have. May be empty.
repeated int32 children = 3 [packed = true];
// list of layers that are z order relative to this layer.
repeated int32 relatives = 4 [packed = true];
- // The type of layer, ex Color, Layer
+ // The type of layer.
+ // Examples: "ContainerLayer", "BufferStateLayer".
optional string type = 5;
optional RegionProto transparent_region = 6;
optional RegionProto visible_region = 7;
@@ -138,7 +144,14 @@
optional RectProto final_crop = 15 [deprecated = true];
optional bool is_opaque = 16;
optional bool invalidate = 17;
+ // Composition states's dataspace.
+ // Examples: "STANDARD_BT709", "STANDARD_BT601_625".
+ // See full enum in
+ // frameworks/native/libs/nativewindow/include/android/data_space.h
optional string dataspace = 18;
+ // Buffer's pixel format
+ // Examples: "PIXEL_FORMAT_TRANSLUCENT", "PIXEL_FORMAT_RGBA_8888".
+ // See full enum in frameworks/native/libs/ui/include/ui/PixelFormat.h
optional string pixel_format = 19;
// The layer's actual color.
optional ColorProto color = 20;
diff --git a/protos/perfetto/trace/ftrace/f2fs.proto b/protos/perfetto/trace/ftrace/f2fs.proto
index 0e59384..eb8be75 100644
--- a/protos/perfetto/trace/ftrace/f2fs.proto
+++ b/protos/perfetto/trace/ftrace/f2fs.proto
@@ -288,3 +288,37 @@
optional uint32 n_wr_s_cnt = 27;
optional uint32 n_wr_s_peak = 28;
}
+message F2fsBackgroundGcFtraceEvent {
+ optional uint64 dev = 1;
+ optional uint32 wait_ms = 2;
+ optional uint32 prefree = 3;
+ optional uint32 free = 4;
+}
+message F2fsGcBeginFtraceEvent {
+ optional uint64 dev = 1;
+ optional uint32 sync = 2;
+ optional uint32 background = 3;
+ optional int64 dirty_nodes = 4;
+ optional int64 dirty_dents = 5;
+ optional int64 dirty_imeta = 6;
+ optional uint32 free_sec = 7;
+ optional uint32 free_seg = 8;
+ optional int32 reserved_seg = 9;
+ optional uint32 prefree_seg = 10;
+ optional int32 gc_type = 11;
+ optional uint32 no_bg_gc = 12;
+ optional uint32 nr_free_secs = 13;
+}
+message F2fsGcEndFtraceEvent {
+ optional uint64 dev = 1;
+ optional int32 ret = 2;
+ optional int32 seg_freed = 3;
+ optional int32 sec_freed = 4;
+ optional int64 dirty_nodes = 5;
+ optional int64 dirty_dents = 6;
+ optional int64 dirty_imeta = 7;
+ optional uint32 free_sec = 8;
+ optional uint32 free_seg = 9;
+ optional int32 reserved_seg = 10;
+ optional uint32 prefree_seg = 11;
+}
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index e0b1eeb..5d5bf06 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -611,5 +611,8 @@
DpuDsiCmdFifoStatusFtraceEvent dpu_dsi_cmd_fifo_status = 492;
DpuDsiRxFtraceEvent dpu_dsi_rx = 493;
DpuDsiTxFtraceEvent dpu_dsi_tx = 494;
+ F2fsBackgroundGcFtraceEvent f2fs_background_gc = 495;
+ F2fsGcBeginFtraceEvent f2fs_gc_begin = 496;
+ F2fsGcEndFtraceEvent f2fs_gc_end = 497;
}
}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index d5cf2d8..c8b4c9e 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -5253,12 +5253,15 @@
// elapsed realtime in nanos since boot of when this entry was logged
optional sfixed64 elapsed_realtime_nanos = 1;
- // where the trace originated
+ // SurfaceFlinger's stage where the snapshot was triggered.
+ // Currently either "visibleRegionsDirty" or "bufferLatched".
optional string where = 2;
optional LayersProto layers = 3;
// Blob for the current HWC information for all layers, reported by dumpsys.
+ // Example:
+ // "maxDownScale: 4, maxFullWidth: 8192, HWState: 1, AssignedState: 3, ..."
optional string hwc_blob = 4;
// Excludes state sent during composition like visible region and composition
@@ -5280,6 +5283,7 @@
message DisplayProto {
optional uint64 id = 1;
+ // Display descriptor, e.g. "Built-In Screen"
optional string name = 2;
optional uint32 layer_stack = 3;
optional SizeProto size = 4;
@@ -5314,12 +5318,14 @@
// unique id per layer.
optional int32 id = 1;
// unique name per layer.
+ // Example: "Wallpaper".
optional string name = 2;
// list of children this layer may have. May be empty.
repeated int32 children = 3 [packed = true];
// list of layers that are z order relative to this layer.
repeated int32 relatives = 4 [packed = true];
- // The type of layer, ex Color, Layer
+ // The type of layer.
+ // Examples: "ContainerLayer", "BufferStateLayer".
optional string type = 5;
optional RegionProto transparent_region = 6;
optional RegionProto visible_region = 7;
@@ -5340,7 +5346,14 @@
optional RectProto final_crop = 15 [deprecated = true];
optional bool is_opaque = 16;
optional bool invalidate = 17;
+ // Composition states's dataspace.
+ // Examples: "STANDARD_BT709", "STANDARD_BT601_625".
+ // See full enum in
+ // frameworks/native/libs/nativewindow/include/android/data_space.h
optional string dataspace = 18;
+ // Buffer's pixel format
+ // Examples: "PIXEL_FORMAT_TRANSLUCENT", "PIXEL_FORMAT_RGBA_8888".
+ // See full enum in frameworks/native/libs/ui/include/ui/PixelFormat.h
optional string pixel_format = 19;
// The layer's actual color.
optional ColorProto color = 20;
@@ -8271,6 +8284,40 @@
optional uint32 n_wr_s_cnt = 27;
optional uint32 n_wr_s_peak = 28;
}
+message F2fsBackgroundGcFtraceEvent {
+ optional uint64 dev = 1;
+ optional uint32 wait_ms = 2;
+ optional uint32 prefree = 3;
+ optional uint32 free = 4;
+}
+message F2fsGcBeginFtraceEvent {
+ optional uint64 dev = 1;
+ optional uint32 sync = 2;
+ optional uint32 background = 3;
+ optional int64 dirty_nodes = 4;
+ optional int64 dirty_dents = 5;
+ optional int64 dirty_imeta = 6;
+ optional uint32 free_sec = 7;
+ optional uint32 free_seg = 8;
+ optional int32 reserved_seg = 9;
+ optional uint32 prefree_seg = 10;
+ optional int32 gc_type = 11;
+ optional uint32 no_bg_gc = 12;
+ optional uint32 nr_free_secs = 13;
+}
+message F2fsGcEndFtraceEvent {
+ optional uint64 dev = 1;
+ optional int32 ret = 2;
+ optional int32 seg_freed = 3;
+ optional int32 sec_freed = 4;
+ optional int64 dirty_nodes = 5;
+ optional int64 dirty_dents = 6;
+ optional int64 dirty_imeta = 7;
+ optional uint32 free_sec = 8;
+ optional uint32 free_seg = 9;
+ optional int32 reserved_seg = 10;
+ optional uint32 prefree_seg = 11;
+}
// End of protos/perfetto/trace/ftrace/f2fs.proto
@@ -10624,6 +10671,9 @@
DpuDsiCmdFifoStatusFtraceEvent dpu_dsi_cmd_fifo_status = 492;
DpuDsiRxFtraceEvent dpu_dsi_rx = 493;
DpuDsiTxFtraceEvent dpu_dsi_tx = 494;
+ F2fsBackgroundGcFtraceEvent f2fs_background_gc = 495;
+ F2fsGcBeginFtraceEvent f2fs_gc_begin = 496;
+ F2fsGcEndFtraceEvent f2fs_gc_end = 497;
}
}
diff --git a/src/tools/ftrace_proto_gen/event_list b/src/tools/ftrace_proto_gen/event_list
index c4b4807..db13230 100644
--- a/src/tools/ftrace_proto_gen/event_list
+++ b/src/tools/ftrace_proto_gen/event_list
@@ -488,4 +488,7 @@
sched/sched_migrate_task
dpu/dsi_cmd_fifo_status
dpu/dsi_rx
-dpu/dsi_tx
\ No newline at end of file
+dpu/dsi_tx
+f2fs/f2fs_background_gc
+f2fs/f2fs_gc_begin
+f2fs/f2fs_gc_end
diff --git a/src/trace_processor/db/column/arrangement_overlay.cc b/src/trace_processor/db/column/arrangement_overlay.cc
index 4f4af85..17063c9 100644
--- a/src/trace_processor/db/column/arrangement_overlay.cc
+++ b/src/trace_processor/db/column/arrangement_overlay.cc
@@ -42,10 +42,7 @@
: inner_(std::move(inner)),
arrangement_(arrangement),
arrangement_state_(arrangement_state),
- does_arrangement_order_storage_(does_arrangement_order_storage) {
- PERFETTO_DCHECK(*std::max_element(arrangement->begin(), arrangement->end()) <=
- inner_->size());
-}
+ does_arrangement_order_storage_(does_arrangement_order_storage) {}
SingleSearchResult ArrangementOverlay::ChainImpl::SingleSearch(
FilterOp op,
diff --git a/src/trace_processor/db/column/numeric_storage.cc b/src/trace_processor/db/column/numeric_storage.cc
index 46ab450..b39b4cb 100644
--- a/src/trace_processor/db/column/numeric_storage.cc
+++ b/src/trace_processor/db/column/numeric_storage.cc
@@ -508,9 +508,6 @@
FilterOp op,
SqlValue sql_val,
const OrderedIndices& indices) const {
- PERFETTO_DCHECK(*std::max_element(indices.data, indices.data + indices.size) <
- size());
-
PERFETTO_TP_TRACE(
metatrace::Category::DB, "NumericStorage::ChainImpl::OrderedIndexSearch",
[indices, op](metatrace::Record* r) {
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
index 0dd3aa9..51917f1 100644
--- a/src/trace_processor/db/query_executor.cc
+++ b/src/trace_processor/db/query_executor.cc
@@ -22,7 +22,6 @@
#include <sys/types.h>
#include "perfetto/base/logging.h"
#include "perfetto/trace_processor/basic_types.h"
-#include "src/trace_processor/containers/bit_vector.h"
#include "src/trace_processor/containers/row_map.h"
#include "src/trace_processor/db/column/data_layer.h"
#include "src/trace_processor/db/column/types.h"
diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc
index 640647b..2163b80 100644
--- a/src/trace_processor/importers/common/track_tracker.cc
+++ b/src/trace_processor/importers/common/track_tracker.cc
@@ -19,7 +19,6 @@
#include <optional>
#include "src/trace_processor/importers/common/args_tracker.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/storage/trace_storage.h"
namespace perfetto {
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index f272e6d..3b846f4 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@
namespace trace_processor {
namespace {
-std::array<FtraceMessageDescriptor, 492> descriptors{{
+std::array<FtraceMessageDescriptor, 498> descriptors{{
{nullptr, 0, {}},
{nullptr, 0, {}},
{nullptr, 0, {}},
@@ -5423,6 +5423,84 @@
{"load", ProtoSchemaType::kUint32},
},
},
+ {
+ "dpu_dsi_cmd_fifo_status",
+ 2,
+ {
+ {},
+ {"header", ProtoSchemaType::kUint32},
+ {"payload", ProtoSchemaType::kUint32},
+ },
+ },
+ {
+ "dpu_dsi_rx",
+ 2,
+ {
+ {},
+ {"cmd", ProtoSchemaType::kUint32},
+ {"rx_buf", ProtoSchemaType::kUint32},
+ },
+ },
+ {
+ "dpu_dsi_tx",
+ 4,
+ {
+ {},
+ {"type", ProtoSchemaType::kUint32},
+ {"tx_buf", ProtoSchemaType::kUint32},
+ {"last", ProtoSchemaType::kUint32},
+ {"delay_ms", ProtoSchemaType::kUint32},
+ },
+ },
+ {
+ "f2fs_background_gc",
+ 4,
+ {
+ {},
+ {"dev", ProtoSchemaType::kUint64},
+ {"wait_ms", ProtoSchemaType::kUint32},
+ {"prefree", ProtoSchemaType::kUint32},
+ {"free", ProtoSchemaType::kUint32},
+ },
+ },
+ {
+ "f2fs_gc_begin",
+ 13,
+ {
+ {},
+ {"dev", ProtoSchemaType::kUint64},
+ {"sync", ProtoSchemaType::kUint32},
+ {"background", ProtoSchemaType::kUint32},
+ {"dirty_nodes", ProtoSchemaType::kInt64},
+ {"dirty_dents", ProtoSchemaType::kInt64},
+ {"dirty_imeta", ProtoSchemaType::kInt64},
+ {"free_sec", ProtoSchemaType::kUint32},
+ {"free_seg", ProtoSchemaType::kUint32},
+ {"reserved_seg", ProtoSchemaType::kInt32},
+ {"prefree_seg", ProtoSchemaType::kUint32},
+ {"gc_type", ProtoSchemaType::kInt32},
+ {"no_bg_gc", ProtoSchemaType::kUint32},
+ {"nr_free_secs", ProtoSchemaType::kUint32},
+ },
+ },
+ {
+ "f2fs_gc_end",
+ 11,
+ {
+ {},
+ {"dev", ProtoSchemaType::kUint64},
+ {"ret", ProtoSchemaType::kInt32},
+ {"seg_freed", ProtoSchemaType::kInt32},
+ {"sec_freed", ProtoSchemaType::kInt32},
+ {"dirty_nodes", ProtoSchemaType::kInt64},
+ {"dirty_dents", ProtoSchemaType::kInt64},
+ {"dirty_imeta", ProtoSchemaType::kInt64},
+ {"free_sec", ProtoSchemaType::kUint32},
+ {"free_seg", ProtoSchemaType::kUint32},
+ {"reserved_seg", ProtoSchemaType::kInt32},
+ {"prefree_seg", ProtoSchemaType::kUint32},
+ },
+ },
}};
} // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index bc832bf..b648907 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -1012,11 +1012,11 @@
break;
}
case FtraceEvent::kFuncgraphEntryFieldNumber: {
- ParseFuncgraphEntry(ts, pid, fld_bytes, seq_state);
+ ParseFuncgraphEntry(ts, cpu, pid, fld_bytes, seq_state);
break;
}
case FtraceEvent::kFuncgraphExitFieldNumber: {
- ParseFuncgraphExit(ts, pid, fld_bytes, seq_state);
+ ParseFuncgraphExit(ts, cpu, pid, fld_bytes, seq_state);
break;
}
case FtraceEvent::kV4l2QbufFieldNumber:
@@ -3194,37 +3194,54 @@
void FtraceParser::ParseFuncgraphEntry(
int64_t timestamp,
+ uint32_t cpu,
uint32_t pid,
protozero::ConstBytes blob,
PacketSequenceStateGeneration* seq_state) {
- // TODO(rsavitski): remove if/when we stop collapsing all idle (swapper)
- // threads to a single track, otherwise this breaks slice nesting.
- if (pid == 0)
- return;
-
protos::pbzero::FuncgraphEntryFtraceEvent::Decoder evt(blob.data, blob.size);
StringId name_id = InternedKernelSymbolOrFallback(evt.func(), seq_state);
- UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
- TrackId track = context_->track_tracker->InternThreadTrack(utid);
+ TrackId track = {};
+ if (pid != 0) {
+ // common case: normal thread
+ UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
+ track = context_->track_tracker->InternThreadTrack(utid);
+ } else {
+ // Idle threads (swapper) are implicit, and all share the same thread id 0.
+ // Therefore we cannot use a thread-scoped track because many instances
+ // of swapper might be running concurrently. Fall back onto global tracks
+ // (one per cpu).
+ base::StackString<255> track_name("swapper%" PRIu32 "-funcgraph", cpu);
+ StringId track_name_id =
+ context_->storage->InternString(track_name.string_view());
+ track = context_->track_tracker->InternCpuTrack(track_name_id, cpu);
+ }
+
context_->slice_tracker->Begin(timestamp, track, kNullStringId, name_id);
}
void FtraceParser::ParseFuncgraphExit(
int64_t timestamp,
+ uint32_t cpu,
uint32_t pid,
protozero::ConstBytes blob,
PacketSequenceStateGeneration* seq_state) {
- // TODO(rsavitski): remove if/when we stop collapsing all idle (swapper)
- // threads to a single track, otherwise this breaks slice nesting.
- if (pid == 0)
- return;
-
protos::pbzero::FuncgraphExitFtraceEvent::Decoder evt(blob.data, blob.size);
StringId name_id = InternedKernelSymbolOrFallback(evt.func(), seq_state);
- UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
- TrackId track = context_->track_tracker->InternThreadTrack(utid);
+ TrackId track = {};
+ if (pid != 0) {
+ // common case: normal thread
+ UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
+ track = context_->track_tracker->InternThreadTrack(utid);
+ } else {
+ // special case: see |ParseFuncgraphEntry|
+ base::StackString<255> track_name("swapper%" PRIu32 "-funcgraph", cpu);
+ StringId track_name_id =
+ context_->storage->InternString(track_name.string_view());
+ track = context_->track_tracker->InternCpuTrack(track_name_id, cpu);
+ }
+
context_->slice_tracker->End(timestamp, track, kNullStringId, name_id);
}
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index 402ad73..6c84c6d 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -237,10 +237,12 @@
void ParseSchedCpuUtilCfs(int64_t timestamp, protozero::ConstBytes);
void ParseFuncgraphEntry(int64_t timestamp,
+ uint32_t cpu,
uint32_t pid,
protozero::ConstBytes blob,
PacketSequenceStateGeneration* seq_state);
void ParseFuncgraphExit(int64_t timestamp,
+ uint32_t cpu,
uint32_t pid,
protozero::ConstBytes blob,
PacketSequenceStateGeneration* seq_state);
diff --git a/src/trace_processor/metrics/sql/android/android_powrails.sql b/src/trace_processor/metrics/sql/android/android_powrails.sql
index 0289350..8c2486a 100644
--- a/src/trace_processor/metrics/sql/android/android_powrails.sql
+++ b/src/trace_processor/metrics/sql/android/android_powrails.sql
@@ -14,13 +14,13 @@
-- limitations under the License.
--
+INCLUDE PERFETTO MODULE android.power_rails;
+
-- View of Power Rail counters with ts converted from ns to ms.
DROP VIEW IF EXISTS power_rails_counters;
CREATE PERFETTO VIEW power_rails_counters AS
-SELECT value, ts / 1000000 AS ts, name
-FROM counter c
-JOIN counter_track t ON c.track_id = t.id
-WHERE name GLOB 'power.*';
+SELECT value, ts / 1000000 AS ts, power_rail_name AS name
+FROM android_power_rails_counters;
DROP VIEW IF EXISTS avg_used_powers;
CREATE PERFETTO VIEW avg_used_powers AS
diff --git a/src/trace_processor/perfetto_sql/engine/created_function.h b/src/trace_processor/perfetto_sql/engine/created_function.h
index c7d44b7..4d744e5 100644
--- a/src/trace_processor/perfetto_sql/engine/created_function.h
+++ b/src/trace_processor/perfetto_sql/engine/created_function.h
@@ -18,20 +18,18 @@
#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_CREATED_FUNCTION_H_
#include <sqlite3.h>
+#include <cstddef>
#include <memory>
-#include <unordered_map>
#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/perfetto_sql/engine/function_util.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
-#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sql_source.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
#include "src/trace_processor/types/destructible.h"
#include "src/trace_processor/util/sql_argument.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class PerfettoSqlEngine;
@@ -59,7 +57,6 @@
static base::Status EnableMemoization(Context*);
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_CREATED_FUNCTION_H_
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
index 8959989..8f163a0 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -45,11 +45,9 @@
#include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
#include "src/trace_processor/sqlite/db_sqlite_table.h"
-#include "src/trace_processor/sqlite/query_cache.h"
#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_engine.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
#include "src/trace_processor/tp_metatrace.h"
#include "src/trace_processor/util/sql_argument.h"
#include "src/trace_processor/util/sql_modules.h"
@@ -169,7 +167,7 @@
} // namespace
PerfettoSqlEngine::PerfettoSqlEngine(StringPool* pool)
- : query_cache_(new QueryCache()), pool_(pool), engine_(new SqliteEngine()) {
+ : pool_(pool), engine_(new SqliteEngine()) {
// Initialize `perfetto_tables` table, which will contain the names of all of
// the registered tables.
char* errmsg_raw = nullptr;
@@ -181,63 +179,75 @@
PERFETTO_FATAL("Failed to initialize perfetto_tables: %s", errmsg_raw);
}
- engine_->RegisterVirtualTableModule<RuntimeTableFunction>(
- "runtime_table_function", this,
- SqliteTableLegacy::TableType::kExplicitCreate, false);
- auto context = std::make_unique<DbSqliteTable::Context>(
- query_cache_.get(),
- [this](const std::string& name) {
- auto* table = runtime_tables_.Find(name);
- PERFETTO_CHECK(table);
- return table->get();
- },
- [this](const std::string& name) {
- bool res = runtime_tables_.Erase(name);
- PERFETTO_CHECK(res);
- });
- engine_->RegisterVirtualTableModule<DbSqliteTable>(
- "runtime_table", std::move(context),
- SqliteTableLegacy::TableType::kExplicitCreate, false);
-}
-
-PerfettoSqlEngine::~PerfettoSqlEngine() {
- // Destroying the sqlite engine should also destroy all the created table
- // functions.
- engine_.reset();
- PERFETTO_CHECK(runtime_table_fn_states_.size() == 0);
- PERFETTO_CHECK(runtime_tables_.size() == 0);
+ {
+ auto ctx = std::make_unique<RuntimeTableFunctionModule::Context>();
+ runtime_table_fn_context_ = ctx.get();
+ engine_->RegisterVirtualTableModule<RuntimeTableFunctionModule>(
+ "runtime_table_function", std::move(ctx));
+ }
+ {
+ auto ctx = std::make_unique<DbSqliteModule::Context>();
+ runtime_table_context_ = ctx.get();
+ engine_->RegisterVirtualTableModule<DbSqliteModule>("runtime_table",
+ std::move(ctx));
+ }
+ {
+ auto ctx = std::make_unique<DbSqliteModule::Context>();
+ static_table_context_ = ctx.get();
+ engine_->RegisterVirtualTableModule<DbSqliteModule>("static_table",
+ std::move(ctx));
+ }
+ {
+ auto ctx = std::make_unique<DbSqliteModule::Context>();
+ static_table_fn_context_ = ctx.get();
+ engine_->RegisterVirtualTableModule<DbSqliteModule>("static_table_function",
+ std::move(ctx));
+ }
}
void PerfettoSqlEngine::RegisterStaticTable(const Table& table,
const std::string& table_name,
Table::Schema schema) {
- auto context = std::make_unique<DbSqliteTable::Context>(
- query_cache_.get(), &table, std::move(schema));
- static_tables_.Insert(table_name, &table);
- engine_->RegisterVirtualTableModule<DbSqliteTable>(
- table_name, std::move(context), SqliteTableLegacy::kEponymousOnly, false);
+ // Make sure we didn't accidentally leak a state from a previous table
+ // creation.
+ PERFETTO_CHECK(!static_table_context_->temporary_create_state);
+ static_table_context_->temporary_create_state =
+ std::make_unique<DbSqliteModule::State>(&table, std::move(schema));
- // Register virtual tables into an internal 'perfetto_tables' table.
- // This is used for iterating through all the tables during a database
- // export.
- char* insert_sql = sqlite3_mprintf(
- "INSERT INTO perfetto_tables(name) VALUES('%q')", table_name.c_str());
- char* error = nullptr;
- sqlite3_exec(engine_->db(), insert_sql, nullptr, nullptr, &error);
- sqlite3_free(insert_sql);
- if (error) {
- PERFETTO_ELOG("Error adding table to perfetto_tables: %s", error);
- sqlite3_free(error);
+ base::StackString<1024> sql(
+ R"(
+ CREATE VIRTUAL TABLE %s USING static_table;
+ INSERT INTO perfetto_tables(name) VALUES('%s');
+ )",
+ table_name.c_str(), table_name.c_str());
+ auto status =
+ Execute(SqlSource::FromTraceProcessorImplementation(sql.ToStdString()));
+ if (!status.ok()) {
+ PERFETTO_FATAL("%s", status.status().c_message());
}
+
+ PERFETTO_CHECK(!static_table_context_->temporary_create_state);
}
void PerfettoSqlEngine::RegisterStaticTableFunction(
std::unique_ptr<StaticTableFunction> fn) {
- std::string table_name = fn->TableName();
- auto context = std::make_unique<DbSqliteTable::Context>(query_cache_.get(),
- std::move(fn));
- engine_->RegisterVirtualTableModule<DbSqliteTable>(
- table_name, std::move(context), SqliteTableLegacy::kEponymousOnly, false);
+ std::string name = fn->TableName();
+
+ // Make sure we didn't accidentally leak a state from a previous table
+ // creation.
+ PERFETTO_CHECK(!static_table_fn_context_->temporary_create_state);
+ static_table_fn_context_->temporary_create_state =
+ std::make_unique<DbSqliteModule::State>(std::move(fn));
+
+ base::StackString<1024> sql(
+ "CREATE VIRTUAL TABLE %s USING static_table_function;", name.c_str());
+ auto status =
+ Execute(SqlSource::FromTraceProcessorImplementation(sql.ToStdString()));
+ if (!status.ok()) {
+ PERFETTO_FATAL("%s", status.status().c_message());
+ }
+
+ PERFETTO_CHECK(!static_table_fn_context_->temporary_create_state);
}
base::StatusOr<PerfettoSqlEngine::ExecutionStats> PerfettoSqlEngine::Execute(
@@ -484,24 +494,38 @@
//
// We would need to do with the transaction API but given we have no usage of
// this until now, investigating that needs some proper work.
- if (runtime_tables_.Find(create_table.name)) {
- if (!create_table.replace) {
- return base::ErrStatus("CREATE PERFETTO TABLE: table '%s' already exists",
- create_table.name.c_str());
- }
-
- base::StackString<1024> drop("DROP TABLE %s", create_table.name.c_str());
- RETURN_IF_ERROR(
- Execute(SqlSource::FromTraceProcessorImplementation(drop.ToStdString()))
- .status());
+ if (create_table.replace) {
+ base::StackString<1024> drop("DROP TABLE IF EXISTS %s",
+ create_table.name.c_str());
+ auto drop_res = Execute(
+ SqlSource::FromTraceProcessorImplementation(drop.ToStdString()));
+ RETURN_IF_ERROR(drop_res.status());
}
- runtime_tables_.Insert(create_table.name, std::move(table));
base::StackString<1024> create("CREATE VIRTUAL TABLE %s USING runtime_table",
create_table.name.c_str());
- return Execute(
- SqlSource::FromTraceProcessorImplementation(create.ToStdString()))
- .status();
+
+ // Make sure we didn't accidentally leak a state from a previous function
+ // creation.
+ PERFETTO_CHECK(!runtime_table_context_->temporary_create_state);
+
+ // Move the state into the context so that it will be picked up in xCreate
+ // of RuntimeTableFunctionModule.
+ runtime_table_context_->temporary_create_state =
+ std::make_unique<DbSqliteModule::State>(std::move(table));
+ auto status =
+ Execute(SqlSource::FromTraceProcessorImplementation(create.ToStdString()))
+ .status();
+
+ // If an error happened, it's possible that the state was not picked up.
+ // Therefore, always reset the state just in case. OTOH if the creation
+ // succeeded, the state should always have been captured.
+ if (status.ok()) {
+ PERFETTO_CHECK(!runtime_table_context_->temporary_create_state);
+ } else {
+ runtime_table_context_->temporary_create_state.reset();
+ }
+ return status;
}
base::Status PerfettoSqlEngine::ExecuteCreateView(
@@ -623,8 +647,9 @@
cf.sql);
}
- std::unique_ptr<RuntimeTableFunction::State> state(
- new RuntimeTableFunction::State{cf.sql, cf.prototype, {}, std::nullopt});
+ auto state = std::make_unique<RuntimeTableFunctionModule::State>(
+ RuntimeTableFunctionModule::State{
+ this, cf.sql, cf.prototype, {}, std::nullopt});
// Parse the return type into a enum format.
{
@@ -702,36 +727,44 @@
state->return_values[i].name().c_str());
}
}
- state->reusable_stmt = std::move(stmt);
+ state->temporary_create_stmt = std::move(stmt);
// TODO(lalitm): this suffers the same non-atomic DROP/CREATE problem as
// CREATE PERFETTO TABLE implementation above: see the comment there for
// more info on this.
- std::string fn_name = state->prototype.function_name;
- std::string lower_name = base::ToLower(state->prototype.function_name);
- if (runtime_table_fn_states_.Find(lower_name)) {
- if (!cf.replace) {
- return base::ErrStatus("Table function named %s already exists",
- state->prototype.function_name.c_str());
- }
- // This will cause |OnTableFunctionDestroyed| below to be executed.
- base::StackString<1024> drop("DROP TABLE %s",
+ if (cf.replace) {
+ base::StackString<1024> drop("DROP TABLE IF EXISTS %s",
state->prototype.function_name.c_str());
auto res = Execute(
SqlSource::FromTraceProcessorImplementation(drop.ToStdString()));
RETURN_IF_ERROR(res.status());
}
- auto it_and_inserted =
- runtime_table_fn_states_.Insert(lower_name, std::move(state));
- PERFETTO_CHECK(it_and_inserted.second);
-
base::StackString<1024> create(
- "CREATE VIRTUAL TABLE %s USING runtime_table_function", fn_name.c_str());
- return Execute(cf.sql.RewriteAllIgnoreExisting(
- SqlSource::FromTraceProcessorImplementation(
- create.ToStdString())))
- .status();
+ "CREATE VIRTUAL TABLE %s USING runtime_table_function",
+ state->prototype.function_name.c_str());
+
+ // Make sure we didn't accidentally leak a state from a previous function
+ // creation.
+ PERFETTO_CHECK(!runtime_table_fn_context_->temporary_create_state);
+
+ // Move the state into the context so that it will be picked up in xCreate
+ // of RuntimeTableFunctionModule.
+ runtime_table_fn_context_->temporary_create_state = std::move(state);
+ auto status = Execute(cf.sql.RewriteAllIgnoreExisting(
+ SqlSource::FromTraceProcessorImplementation(
+ create.ToStdString())))
+ .status();
+
+ // If an error happened, it's possible that the state was not picked up.
+ // Therefore, always reset the state just in case. OTOH if the creation
+ // succeeded, the state should always have been captured.
+ if (status.ok()) {
+ PERFETTO_CHECK(!runtime_table_fn_context_->temporary_create_state);
+ } else {
+ runtime_table_fn_context_->temporary_create_state.reset();
+ }
+ return status;
}
base::Status PerfettoSqlEngine::ExecuteCreateMacro(
@@ -782,18 +815,6 @@
return base::OkStatus();
}
-RuntimeTableFunction::State* PerfettoSqlEngine::GetRuntimeTableFunctionState(
- const std::string& name) const {
- auto* it = runtime_table_fn_states_.Find(base::ToLower(name));
- PERFETTO_CHECK(it);
- return it->get();
-}
-
-void PerfettoSqlEngine::OnRuntimeTableFunctionDestroyed(
- const std::string& name) {
- PERFETTO_CHECK(runtime_table_fn_states_.Erase(base::ToLower(name)));
-}
-
base::StatusOr<std::vector<std::string>>
PerfettoSqlEngine::GetColumnNamesFromSelectStatement(
const SqliteEngine::PreparedStatement& stmt,
@@ -882,14 +903,14 @@
const RuntimeTable* PerfettoSqlEngine::GetRuntimeTableOrNull(
std::string_view name) const {
- auto* table_ptr = runtime_tables_.Find(name.data());
- return table_ptr ? table_ptr->get() : nullptr;
+ auto* state = runtime_table_context_->manager.FindStateByName(name);
+ return state ? state->runtime_table.get() : nullptr;
}
const Table* PerfettoSqlEngine::GetStaticTableOrNull(
std::string_view name) const {
- auto* table_ptr = static_tables_.Find(name.data());
- return table_ptr ? *table_ptr : nullptr;
+ auto* state = static_table_context_->manager.FindStateByName(name);
+ return state ? state->static_table : nullptr;
}
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
index ea785b6..1d7642f 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
@@ -38,10 +38,9 @@
#include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
-#include "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h"
#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
#include "src/trace_processor/sqlite/bindings/sqlite_window_function.h"
-#include "src/trace_processor/sqlite/query_cache.h"
+#include "src/trace_processor/sqlite/db_sqlite_table.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_engine.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
@@ -66,7 +65,6 @@
};
explicit PerfettoSqlEngine(StringPool* pool);
- ~PerfettoSqlEngine();
// Executes all the statements in |sql| and returns a |ExecutionResult|
// object. The metadata will reference all the statements executed and the
@@ -173,13 +171,6 @@
// Registers a trace processor C++ table function with SQLite.
void RegisterStaticTableFunction(std::unique_ptr<StaticTableFunction> fn);
- // Returns the state for the given table function.
- RuntimeTableFunction::State* GetRuntimeTableFunctionState(
- const std::string&) const;
-
- // Should be called when a table function is destroyed.
- void OnRuntimeTableFunctionDestroyed(const std::string&);
-
SqliteEngine* sqlite_engine() { return engine_.get(); }
// Makes new SQL module available to import.
@@ -270,7 +261,6 @@
const std::string& key,
const PerfettoSqlParser& parser);
- std::unique_ptr<QueryCache> query_cache_;
StringPool* pool_ = nullptr;
uint64_t static_function_count_ = 0;
@@ -278,10 +268,10 @@
uint64_t static_window_function_count_ = 0;
uint64_t runtime_function_count_ = 0;
- base::FlatHashMap<std::string, std::unique_ptr<RuntimeTableFunction::State>>
- runtime_table_fn_states_;
- base::FlatHashMap<std::string, const Table*> static_tables_;
- base::FlatHashMap<std::string, std::unique_ptr<RuntimeTable>> runtime_tables_;
+ RuntimeTableFunctionModule::Context* runtime_table_fn_context_ = nullptr;
+ DbSqliteModule::Context* runtime_table_context_ = nullptr;
+ DbSqliteModule::Context* static_table_context_ = nullptr;
+ DbSqliteModule::Context* static_table_fn_context_ = nullptr;
base::FlatHashMap<std::string, sql_modules::RegisteredModule> modules_;
base::FlatHashMap<std::string, PerfettoSqlPreprocessor::Macro> macros_;
std::unique_ptr<SqliteEngine> engine_;
diff --git a/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
index 3d0af93..12faa70 100644
--- a/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
@@ -16,15 +16,28 @@
#include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
+#include <sqlite3.h>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
#include <optional>
+#include <string>
#include <utility>
+#include <vector>
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/public/compiler.h"
+#include "src/trace_processor/perfetto_sql/engine/function_util.h"
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
-#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/tp_metatrace.h"
+#include "src/trace_processor/util/sql_argument.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
namespace {
@@ -33,217 +46,226 @@
sqlite3_clear_bindings(stmt);
}
-} // namespace
-
-RuntimeTableFunction::RuntimeTableFunction(sqlite3*, PerfettoSqlEngine* engine)
- : engine_(engine) {}
-
-RuntimeTableFunction::~RuntimeTableFunction() {
- engine_->OnRuntimeTableFunctionDestroyed(name());
-}
-
-base::Status RuntimeTableFunction::Init(int,
- const char* const*,
- Schema* schema) {
- state_ = engine_->GetRuntimeTableFunctionState(name());
-
- // Now we've parsed prototype and return values, create the schema.
- *schema = CreateSchema();
- return base::OkStatus();
-}
-
-SqliteTableLegacy::Schema RuntimeTableFunction::CreateSchema() {
- std::vector<Column> columns;
- for (size_t i = 0; i < state_->return_values.size(); ++i) {
- const auto& ret = state_->return_values[i];
- columns.push_back(Column(columns.size(), ret.name().ToStdString(),
+auto CreateTableStrFromState(RuntimeTableFunctionModule::State* state) {
+ std::vector<std::string> columns;
+ columns.reserve(state->return_values.size());
+ for (const auto& ret : state->return_values) {
+ columns.emplace_back(ret.name().ToStdString() + " " +
+ sqlite::utils::SqlValueTypeToString(
sql_argument::TypeToSqlValueType(ret.type())));
}
- for (size_t i = 0; i < state_->prototype.arguments.size(); ++i) {
- const auto& arg = state_->prototype.arguments[i];
-
+ for (const auto& arg : state->prototype.arguments) {
// Add the "in_" prefix to every argument param to avoid clashes between the
// output and input parameters.
- columns.push_back(Column(columns.size(), "in_" + arg.name().ToStdString(),
- sql_argument::TypeToSqlValueType(arg.type()),
- true));
+ columns.emplace_back("in_" + arg.name().ToStdString() + " " +
+ sqlite::utils::SqlValueTypeToString(
+ sql_argument::TypeToSqlValueType(arg.type())) +
+ " HIDDEN");
}
+ columns.emplace_back("_primary_key BIGINT HIDDEN");
- std::vector<size_t> primary_keys;
-
- // Add the "primary key" column. SQLite requires that we provide a column
- // which is non-null and unique. Unfortunately, we have no restrictions on
- // the subqueries so we cannot rely on this constraint being held there.
- // Therefore, we create a "primary key" column which exists purely for SQLite
- // primary key purposes and is equal to the row number.
- columns.push_back(
- Column(columns.size(), "_primary_key", SqlValue::kLong, true));
- primary_keys.emplace_back(columns.size() - 1);
-
- return SqliteTableLegacy::Schema(std::move(columns), std::move(primary_keys));
+ std::string cols = base::Join(columns, ",");
+ return base::StackString<1024>(
+ R"(CREATE TABLE x(%s, PRIMARY KEY(_primary_key)) WITHOUT ROWID)",
+ cols.c_str());
}
-std::unique_ptr<SqliteTableLegacy::BaseCursor>
-RuntimeTableFunction::CreateCursor() {
- return std::unique_ptr<Cursor>(new Cursor(this, state_));
+} // namespace
+
+int RuntimeTableFunctionModule::Create(sqlite3* db,
+ void* ctx,
+ int,
+ const char* const* argv,
+ sqlite3_vtab** vtab,
+ char**) {
+ auto* context = GetContext(ctx);
+ auto state = std::move(context->temporary_create_state);
+
+ auto create_table_str = CreateTableStrFromState(state.get());
+ if (int ret = sqlite3_declare_vtab(db, create_table_str.c_str());
+ ret != SQLITE_OK) {
+ return ret;
+ }
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ res->reusable_stmt = std::move(state->temporary_create_stmt);
+ state->temporary_create_stmt = std::nullopt;
+ res->state = context->manager.OnCreate(argv, std::move(state));
+ *vtab = res.release();
+ return SQLITE_OK;
}
-int RuntimeTableFunction::BestIndex(const QueryConstraints& qc,
- BestIndexInfo* info) {
- // Only accept constraint sets where every input parameter has a value.
- size_t seen_argument_constraints = 0;
- for (size_t i = 0; i < qc.constraints().size(); ++i) {
- const auto& cs = qc.constraints()[i];
- seen_argument_constraints +=
- state_->IsArgumentColumn(static_cast<size_t>(cs.column));
+int RuntimeTableFunctionModule::Destroy(sqlite3_vtab* vtab) {
+ std::unique_ptr<Vtab> tab(GetVtab(vtab));
+ sqlite::ModuleStateManager<RuntimeTableFunctionModule>::OnDestroy(tab->state);
+ return SQLITE_OK;
+}
+
+int RuntimeTableFunctionModule::Connect(sqlite3* db,
+ void* ctx,
+ int,
+ const char* const*,
+ sqlite3_vtab** vtab,
+ char** argv) {
+ auto* context = GetContext(ctx);
+
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ res->state = context->manager.OnConnect(argv);
+
+ auto create_table_str = CreateTableStrFromState(
+ sqlite::ModuleStateManager<RuntimeTableFunctionModule>::GetState(
+ res->state));
+ if (int ret = sqlite3_declare_vtab(db, create_table_str.c_str());
+ ret != SQLITE_OK) {
+ // If the registration happens to fail, make sure to disconnect the state
+ // again.
+ sqlite::ModuleStateManager<RuntimeTableFunctionModule>::OnDisconnect(
+ res->state);
+ return ret;
}
- if (seen_argument_constraints < state_->prototype.arguments.size())
+ *vtab = res.release();
+ return SQLITE_OK;
+}
+
+int RuntimeTableFunctionModule::Disconnect(sqlite3_vtab* vtab) {
+ std::unique_ptr<Vtab> tab(GetVtab(vtab));
+ sqlite::ModuleStateManager<RuntimeTableFunctionModule>::OnDisconnect(
+ tab->state);
+ return SQLITE_OK;
+}
+
+int RuntimeTableFunctionModule::BestIndex(sqlite3_vtab* tab,
+ sqlite3_index_info* info) {
+ auto* t = GetVtab(tab);
+ auto* s = sqlite::ModuleStateManager<RuntimeTableFunctionModule>::GetState(
+ t->state);
+
+ // Don't deal with any constraints on the output parameters for simplicty.
+ // TODO(lalitm): reconsider this decision to allow more efficient queries:
+ // we would need to wrap the query in a SELECT * FROM (...) WHERE constraint
+ // like we do for SPAN JOIN.
+ base::Status status = sqlite::utils::ValidateFunctionArguments(
+ info, s->prototype.arguments.size(),
+ [s](size_t c) { return s->IsArgumentColumn(c); });
+ if (!status.ok()) {
return SQLITE_CONSTRAINT;
-
- for (size_t i = 0; i < info->sqlite_omit_constraint.size(); ++i) {
- size_t col = static_cast<size_t>(qc.constraints()[i].column);
- if (state_->IsArgumentColumn(col)) {
- info->sqlite_omit_constraint[i] = true;
- }
}
return SQLITE_OK;
}
-RuntimeTableFunction::Cursor::Cursor(RuntimeTableFunction* table, State* state)
- : SqliteTableLegacy::BaseCursor(table), table_(table), state_(state) {
- if (state->reusable_stmt) {
- stmt_ = std::move(state->reusable_stmt);
- state->reusable_stmt = std::nullopt;
- return_stmt_to_state_ = true;
+int RuntimeTableFunctionModule::Open(sqlite3_vtab* tab,
+ sqlite3_vtab_cursor** cursor) {
+ auto* t = GetVtab(tab);
+ std::unique_ptr<Cursor> c = std::make_unique<Cursor>();
+ if (t->reusable_stmt) {
+ c->stmt = std::move(t->reusable_stmt);
+ t->reusable_stmt = std::nullopt;
}
+ *cursor = c.release();
+ return SQLITE_OK;
}
-RuntimeTableFunction::Cursor::~Cursor() {
- if (return_stmt_to_state_) {
- ResetStatement(stmt_->sqlite_stmt());
- state_->reusable_stmt = std::move(stmt_);
+int RuntimeTableFunctionModule::Close(sqlite3_vtab_cursor* cursor) {
+ std::unique_ptr<Cursor> c(GetCursor(cursor));
+ auto* t = GetVtab(c->pVtab);
+ if (!t->reusable_stmt && c->stmt) {
+ ResetStatement(c->stmt->sqlite_stmt());
+ t->reusable_stmt = std::move(c->stmt);
}
+ return SQLITE_OK;
}
-base::Status RuntimeTableFunction::Cursor::Filter(const QueryConstraints& qc,
- sqlite3_value** argv,
- FilterHistory) {
+int RuntimeTableFunctionModule::Filter(sqlite3_vtab_cursor* cur,
+ int,
+ const char*,
+ int argc,
+ sqlite3_value** argv) {
+ auto* c = GetCursor(cur);
+ auto* t = GetVtab(cur->pVtab);
+ auto* s = sqlite::ModuleStateManager<RuntimeTableFunctionModule>::GetState(
+ t->state);
+
+ PERFETTO_CHECK(static_cast<size_t>(argc) == s->prototype.arguments.size());
PERFETTO_TP_TRACE(metatrace::Category::FUNCTION_CALL, "TABLE_FUNCTION_CALL",
- [this](metatrace::Record* r) {
- r->AddArg("Function",
- state_->prototype.function_name.c_str());
+ [s](metatrace::Record* r) {
+ r->AddArg("Function", s->prototype.function_name.c_str());
});
- auto col_to_arg_idx = [this](int col) {
- return static_cast<uint32_t>(col) -
- static_cast<uint32_t>(state_->return_values.size());
- };
-
- size_t seen_argument_constraints = 0;
- for (size_t i = 0; i < qc.constraints().size(); ++i) {
- const auto& cs = qc.constraints()[i];
-
- // Only consider argument columns (i.e. input parameters) as we're
- // delegating the rest to SQLite.
- if (!state_->IsArgumentColumn(static_cast<size_t>(cs.column)))
- continue;
-
- // We only support equality constraints as we're expecting "input arguments"
- // to our "function".
- if (!sqlite::utils::IsOpEq(cs.op)) {
- return base::ErrStatus("%s: non-equality constraint passed",
- state_->prototype.function_name.c_str());
- }
-
- const auto& arg = state_->prototype.arguments[col_to_arg_idx(cs.column)];
- base::Status status = sqlite::utils::TypeCheckSqliteValue(
- argv[i], sql_argument::TypeToSqlValueType(arg.type()),
- sql_argument::TypeToHumanFriendlyString(arg.type()));
- if (!status.ok()) {
- return base::ErrStatus("%s: argument %s (index %zu) %s",
- state_->prototype.function_name.c_str(),
- arg.name().c_str(), i, status.c_message());
- }
-
- seen_argument_constraints++;
- }
-
- // Verify that we saw one valid constraint for every input argument.
- if (seen_argument_constraints < state_->prototype.arguments.size()) {
- return base::ErrStatus(
- "%s: missing value for input argument. Saw %zu arguments but expected "
- "%zu",
- state_->prototype.function_name.c_str(), seen_argument_constraints,
- state_->prototype.arguments.size());
- }
-
// Prepare the SQL definition as a statement using SQLite.
// TODO(lalitm): measure and implement whether it would be a good idea to
// forward constraints here when we build the nested query.
- if (stmt_) {
+ if (c->stmt) {
// Filter can be called multiple times for the same cursor, so if we
// already have a statement, reset and reuse it. Otherwise, create a
// new one.
- ResetStatement(stmt_->sqlite_stmt());
+ ResetStatement(c->stmt->sqlite_stmt());
} else {
- auto stmt = table_->engine_->sqlite_engine()->PrepareStatement(
- state_->sql_defn_str);
- RETURN_IF_ERROR(stmt.status());
- stmt_ = std::move(stmt);
+ auto stmt = s->engine->sqlite_engine()->PrepareStatement(s->sql_defn_str);
+ c->stmt = std::move(stmt);
+ if (const auto& status = c->stmt->status(); !status.ok()) {
+ return sqlite::utils::SetError(t, status.c_message());
+ }
}
// Bind all the arguments to the appropriate places in the function.
- for (size_t i = 0; i < qc.constraints().size(); ++i) {
- const auto& cs = qc.constraints()[i];
-
- // Don't deal with any constraints on the output parameters for simplicty.
- // TODO(lalitm): reconsider this decision to allow more efficient queries:
- // we would need to wrap the query in a SELECT * FROM (...) WHERE constraint
- // like we do for SPAN JOIN.
- if (!state_->IsArgumentColumn(static_cast<size_t>(cs.column)))
- continue;
-
- uint32_t index = col_to_arg_idx(cs.column);
- PERFETTO_DCHECK(index < state_->prototype.arguments.size());
-
- const auto& arg = state_->prototype.arguments[index];
- auto status = MaybeBindArgument(
- stmt_->sqlite_stmt(), state_->prototype.function_name, arg, argv[i]);
- RETURN_IF_ERROR(status);
+ for (uint32_t i = 0; i < static_cast<uint32_t>(argc); ++i) {
+ const auto& arg = s->prototype.arguments[i];
+ base::Status status = MaybeBindArgument(
+ c->stmt->sqlite_stmt(), s->prototype.function_name, arg, argv[i]);
+ if (!status.ok()) {
+ return sqlite::utils::SetError(t, status.c_message());
+ }
}
// Reset the next call count - this is necessary because the same cursor
// can be used for multiple filter operations.
- next_call_count_ = 0;
- return Next();
+ c->next_call_count = 0;
+ return Next(cur);
}
-base::Status RuntimeTableFunction::Cursor::Next() {
- is_eof_ = !stmt_->Step();
- next_call_count_++;
- return stmt_->status();
+int RuntimeTableFunctionModule::Next(sqlite3_vtab_cursor* cur) {
+ auto* c = GetCursor(cur);
+ c->is_eof = !c->stmt->Step();
+ c->next_call_count++;
+ if (const auto& status = c->stmt->status(); !status.ok()) {
+ return sqlite::utils::SetError(cur->pVtab, status.c_message());
+ }
+ return SQLITE_OK;
}
-bool RuntimeTableFunction::Cursor::Eof() {
- return is_eof_;
+int RuntimeTableFunctionModule::Eof(sqlite3_vtab_cursor* cur) {
+ return GetCursor(cur)->is_eof;
}
-base::Status RuntimeTableFunction::Cursor::Column(sqlite3_context* ctx, int i) {
- size_t idx = static_cast<size_t>(i);
- if (state_->IsReturnValueColumn(idx)) {
- sqlite::result::Value(ctx, sqlite3_column_value(stmt_->sqlite_stmt(), i));
- } else if (state_->IsArgumentColumn(idx)) {
+int RuntimeTableFunctionModule::Column(sqlite3_vtab_cursor* cur,
+ sqlite3_context* ctx,
+ int N) {
+ auto* c = GetCursor(cur);
+ auto* t = GetVtab(cur->pVtab);
+ auto* s = sqlite::ModuleStateManager<RuntimeTableFunctionModule>::GetState(
+ t->state);
+
+ auto idx = static_cast<size_t>(N);
+ if (PERFETTO_LIKELY(s->IsReturnValueColumn(idx))) {
+ sqlite::result::Value(ctx, sqlite3_column_value(c->stmt->sqlite_stmt(), N));
+ return SQLITE_OK;
+ }
+
+ if (PERFETTO_LIKELY(s->IsArgumentColumn(idx))) {
// TODO(lalitm): it may be more appropriate to keep a note of the arguments
// which we passed in and return them here. Not doing this to because it
// doesn't seem necessary for any useful thing but something which may need
// to be changed in the future.
sqlite::result::Null(ctx);
- } else {
- PERFETTO_DCHECK(state_->IsPrimaryKeyColumn(idx));
- sqlite::result::Long(ctx, next_call_count_);
+ return SQLITE_OK;
}
- return base::OkStatus();
+
+ PERFETTO_DCHECK(s->IsPrimaryKeyColumn(idx));
+ sqlite::result::Long(ctx, c->next_call_count);
+ return SQLITE_OK;
}
-} // namespace trace_processor
-} // namespace perfetto
+int RuntimeTableFunctionModule::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) {
+ return SQLITE_ERROR;
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/engine/runtime_table_function.h b/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
index 80edf72..37abd2a 100644
--- a/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
+++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
@@ -17,31 +17,36 @@
#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_RUNTIME_TABLE_FUNCTION_H_
#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_RUNTIME_TABLE_FUNCTION_H_
+#include <cstddef>
+#include <cstdint>
+#include <memory>
#include <optional>
+#include <vector>
+#include "perfetto/base/logging.h"
#include "src/trace_processor/perfetto_sql/engine/function_util.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
+#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_engine.h"
+#include "src/trace_processor/util/sql_argument.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class PerfettoSqlEngine;
// The implementation of the SqliteTableLegacy interface for table functions
// defined at runtime using SQL.
-class RuntimeTableFunction final
- : public TypedSqliteTable<RuntimeTableFunction, PerfettoSqlEngine*> {
- public:
- // The state of this function. This is separated from |RuntimeTableFunction|
- // because |RuntimeTableFunction| is owned by Sqlite while |State| is owned by
- // PerfettoSqlEngine.
+struct RuntimeTableFunctionModule
+ : public sqlite::Module<RuntimeTableFunctionModule> {
struct State {
+ PerfettoSqlEngine* engine;
SqlSource sql_defn_str;
FunctionPrototype prototype;
std::vector<sql_argument::ArgumentDefinition> return_values;
- std::optional<SqliteEngine::PreparedStatement> reusable_stmt;
+ std::optional<SqliteEngine::PreparedStatement> temporary_create_stmt;
bool IsReturnValueColumn(size_t i) const {
PERFETTO_DCHECK(i < TotalColumnCount());
@@ -65,44 +70,55 @@
kPrimaryKeyColumns;
}
};
- class Cursor final : public SqliteTableLegacy::BaseCursor {
- public:
- explicit Cursor(RuntimeTableFunction* table, State* state);
- ~Cursor() final;
-
- base::Status Filter(const QueryConstraints& qc,
- sqlite3_value**,
- FilterHistory);
- base::Status Next();
- bool Eof();
- base::Status Column(sqlite3_context* context, int N);
-
- private:
- RuntimeTableFunction* table_ = nullptr;
- State* state_ = nullptr;
-
- std::optional<SqliteEngine::PreparedStatement> stmt_;
- bool return_stmt_to_state_ = false;
-
- bool is_eof_ = false;
- int next_call_count_ = 0;
+ struct Context {
+ std::unique_ptr<State> temporary_create_state;
+ sqlite::ModuleStateManager<RuntimeTableFunctionModule> manager;
+ };
+ struct Vtab : sqlite::Module<RuntimeTableFunctionModule>::Vtab {
+ sqlite::ModuleStateManager<RuntimeTableFunctionModule>::PerVtabState* state;
+ std::optional<SqliteEngine::PreparedStatement> reusable_stmt;
+ };
+ struct Cursor : sqlite::Module<RuntimeTableFunctionModule>::Cursor {
+ std::optional<SqliteEngine::PreparedStatement> stmt;
+ bool is_eof = false;
+ int next_call_count = 0;
};
- RuntimeTableFunction(sqlite3*, PerfettoSqlEngine*);
- ~RuntimeTableFunction() final;
+ static constexpr bool kSupportsWrites = false;
+ static constexpr bool kDoesOverloadFunctions = false;
- base::Status Init(int argc, const char* const* argv, Schema*) final;
- std::unique_ptr<SqliteTableLegacy::BaseCursor> CreateCursor() final;
- int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) final;
+ static int Create(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Destroy(sqlite3_vtab*);
- private:
- Schema CreateSchema();
+ static int Connect(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Disconnect(sqlite3_vtab*);
- PerfettoSqlEngine* engine_ = nullptr;
- State* state_ = nullptr;
+ static int BestIndex(sqlite3_vtab*, sqlite3_index_info*);
+
+ static int Open(sqlite3_vtab*, sqlite3_vtab_cursor**);
+ static int Close(sqlite3_vtab_cursor*);
+
+ static int Filter(sqlite3_vtab_cursor*,
+ int,
+ const char*,
+ int,
+ sqlite3_value**);
+ static int Next(sqlite3_vtab_cursor*);
+ static int Eof(sqlite3_vtab_cursor*);
+ static int Column(sqlite3_vtab_cursor*, sqlite3_context*, int);
+ static int Rowid(sqlite3_vtab_cursor*, sqlite_int64*);
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_RUNTIME_TABLE_FUNCTION_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h b/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h
index 5e54006..aaecb04 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h
@@ -18,14 +18,13 @@
#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_CREATE_FUNCTION_H_
#include <sqlite3.h>
-#include <unordered_map>
+#include <cstddef>
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
-#include "src/trace_processor/sqlite/scoped_db.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class PerfettoSqlEngine;
@@ -61,7 +60,6 @@
Destructors&);
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_CREATE_FUNCTION_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc
index 44782e7..f1483ea 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc
@@ -15,24 +15,22 @@
*/
#include "src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.h"
-
-#include <numeric>
-#include <optional>
+#include <cstddef>
+#include <string>
+#include <utility>
#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/null_term_string_view.h"
#include "src/trace_processor/perfetto_sql/engine/function_util.h"
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
-#include "src/trace_processor/sqlite/scoped_db.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
+#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
-#include "src/trace_processor/tp_metatrace.h"
#include "src/trace_processor/util/status_macros.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
base::Status CreateViewFunction::Run(CreateViewFunction::Context* ctx,
size_t argc,
@@ -100,5 +98,4 @@
return res.status();
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.h b/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.h
index a4131dd..89bf9fc 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.h
@@ -18,12 +18,13 @@
#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_CREATE_VIEW_FUNCTION_H_
#include <sqlite3.h>
-#include <unordered_map>
+#include <cstddef>
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class PerfettoSqlEngine;
@@ -42,7 +43,6 @@
Destructors&);
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_CREATE_VIEW_FUNCTION_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/import.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/import.cc
index a660723..df0e014 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/import.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/import.cc
@@ -16,22 +16,15 @@
#include "src/trace_processor/perfetto_sql/intrinsics/functions/import.h"
-#include <numeric>
+#include <cstddef>
#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_utils.h"
-#include "perfetto/ext/base/string_view.h"
#include "perfetto/trace_processor/basic_types.h"
-#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sql_source.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
-#include "src/trace_processor/tp_metatrace.h"
-#include "src/trace_processor/util/sql_modules.h"
-#include "src/trace_processor/util/status_macros.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
base::Status Import::Run(Import::Context* ctx,
size_t argc,
@@ -65,5 +58,4 @@
.status();
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
index ce41792..49ad289 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
@@ -755,7 +755,6 @@
auto* context = GetContext(ctx);
std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
res->state = context->manager.OnConnect(argv);
- *vtab = res.release();
auto* state =
sqlite::ModuleStateManager<SpanJoinOperatorModule>::GetState(res->state);
@@ -763,6 +762,7 @@
ret != SQLITE_OK) {
return ret;
}
+ *vtab = res.release();
return SQLITE_OK;
}
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
index 23fc9f1..ef4a58a 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
@@ -62,7 +62,6 @@
"../../../importers/proto:full",
"../../../importers/proto:minimal",
"../../../sqlite",
- "../../../sqlite:query_constraints",
"../../../storage",
"../../../tables",
"../../../types",
@@ -82,7 +81,6 @@
"../../../../../include/perfetto/trace_processor:basic_types",
"../../../../base",
"../../../db",
- "../../../sqlite:query_constraints",
]
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
index 73bde66..4c492d1 100644
--- a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
@@ -36,6 +36,7 @@
"stack_trace",
"time",
"v8",
+ "wattson",
]
generated_header = "stdlib.h"
namespace = "stdlib"
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
index 6eda0bc..786882e 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
@@ -27,6 +27,7 @@
"binder.sql",
"broadcasts.sql",
"critical_blocking_calls.sql",
+ "device.sql",
"dvfs.sql",
"freezer.sql",
"garbage_collection.sql",
@@ -36,6 +37,7 @@
"monitor_contention.sql",
"network_packets.sql",
"oom_adjuster.sql",
+ "power_rails.sql",
"process_metadata.sql",
"screenshots.sql",
"services.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/device.sql b/src/trace_processor/perfetto_sql/stdlib/android/device.sql
new file mode 100644
index 0000000..55e8365
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/device.sql
@@ -0,0 +1,42 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed 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
+--
+-- https://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.
+
+-- Extract name of the device based on metadata from the trace.
+CREATE PERFETTO TABLE android_device_name(
+ -- Device name.
+ name STRING
+)
+AS
+WITH
+ -- Example str_value:
+ -- Android/aosp_raven/raven:VanillaIceCream/UDC/11197703:userdebug/test-keys
+ -- Gets substring after first slash;
+ after_first_slash(str) AS (
+ SELECT SUBSTR(str_value, INSTR(str_value, '/') + 1)
+ FROM metadata
+ WHERE name = 'android_build_fingerprint'
+ ),
+ -- Gets substring after second slash
+ after_second_slash(str) AS (
+ SELECT SUBSTR(str, INSTR(str, '/') + 1)
+ FROM after_first_slash
+ ),
+ -- Gets substring after second slash and before the colon
+ before_colon(str) AS (
+ SELECT SUBSTR(str, 0, INSTR(str, ':'))
+ FROM after_second_slash
+ )
+SELECT str AS name FROM before_colon;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql b/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql
new file mode 100644
index 0000000..6630c53
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql
@@ -0,0 +1,36 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed 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
+--
+-- https://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.
+--
+
+-- Android power rails counters.
+CREATE PERFETTO TABLE android_power_rails_counters (
+ -- `counter.id`
+ id INT,
+ -- Counter timestamp.
+ ts INT,
+ -- Power rail name. From `counter_track.name`.
+ power_rail_name INT,
+ -- Power rails counter value in micro watts.
+ value DOUBLE
+)
+AS
+SELECT
+ c.id,
+ c.ts,
+ t.name AS power_rail_name,
+ c.value
+FROM counter c
+JOIN counter_track t ON c.track_id = t.id
+WHERE name GLOB 'power.*';
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn
index e17e6b2..28fc5a0 100644
--- a/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn
@@ -17,6 +17,8 @@
perfetto_sql_source_set("cpu") {
sources = [
"cpus.sql",
+ "freq.sql",
+ "idle.sql",
"size.sql",
]
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql b/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql
new file mode 100644
index 0000000..c0226bf
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql
@@ -0,0 +1,51 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed 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
+--
+-- https://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.
+
+INCLUDE PERFETTO MODULE counters.intervals;
+
+-- Counter information for each frequency change for each CPU. Finds each time
+-- region where a CPU frequency is constant.
+CREATE PERFETTO TABLE cpu_freq_counters(
+ -- Counter id.
+ id INT,
+ -- Joinable with 'counter_track.id'.
+ track_id INT,
+ -- Starting timestamp of the counter
+ ts LONG,
+ -- Duration in which counter is constant and frequency doesn't change.
+ dur INT,
+ -- Frequency in kHz of the CPU that corresponds to this counter. NULL if not
+ -- found or undefined.
+ freq INT,
+ -- CPU that corresponds to this counter.
+ cpu INT
+) AS
+SELECT
+ count_w_dur.id,
+ count_w_dur.track_id,
+ count_w_dur.ts,
+ count_w_dur.dur,
+ count_w_dur.value as freq,
+ cct.cpu
+FROM
+counter_leading_intervals!((
+ SELECT c.*
+ FROM counter c
+ JOIN cpu_counter_track cct
+ ON cct.id = c.track_id AND cct.name = 'cpufreq'
+)) count_w_dur
+JOIN cpu_counter_track cct
+ON track_id = cct.id;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql b/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql
new file mode 100644
index 0000000..7f837a2
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql
@@ -0,0 +1,53 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed 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
+--
+-- https://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.
+
+INCLUDE PERFETTO MODULE counters.intervals;
+
+-- Counter information for each idle state change for each CPU. Finds each time
+-- region where a CPU idle state is constant.
+CREATE PERFETTO TABLE cpu_idle_counters(
+ -- Counter id.
+ id INT,
+ -- Joinable with 'counter_track.id'.
+ track_id INT,
+ -- Starting timestamp of the counter.
+ ts LONG,
+ -- Duration in which the counter is contant and idle state doesn't change.
+ dur INT,
+ -- Idle state of the CPU that corresponds to this counter. An idle state of -1
+ -- is defined to be active state for the CPU, and the larger the integer, the
+ -- deeper the idle state of the CPU. NULL if not found or undefined.
+ idle INT,
+ -- CPU that corresponds to this counter.
+ cpu INT
+)
+AS
+SELECT
+ count_w_dur.id,
+ count_w_dur.track_id,
+ count_w_dur.ts,
+ count_w_dur.dur,
+ IIF(count_w_dur.value = 4294967295, -1, count_w_dur.value) AS idle,
+ cct.cpu
+FROM
+counter_leading_intervals!((
+ SELECT c.*
+ FROM counter c
+ JOIN cpu_counter_track cct
+ ON cct.id = c.track_id and cct.name = 'cpuidle'
+)) count_w_dur
+JOIN cpu_counter_track cct
+ON track_id = cct.id;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn
new file mode 100644
index 0000000..4167d8c
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed 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.
+
+import("../../../../../gn/perfetto_sql.gni")
+
+perfetto_sql_source_set("wattson") {
+ sources = [
+ "arm_dsu.sql",
+ "cpu_freq.sql",
+ "cpu_idle.sql",
+ "system_state.sql",
+ ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql
new file mode 100644
index 0000000..4f0572d
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql
@@ -0,0 +1,49 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed 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
+--
+-- https://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.
+
+-- Converts event counter from count to rate (num of accesses per ns).
+CREATE PERFETTO FUNCTION _get_rate(event STRING)
+RETURNS TABLE(ts LONG, dur INT, access_rate INT)
+AS
+SELECT
+ ts,
+ lead(ts) OVER (PARTITION BY track_id ORDER BY ts) - ts AS dur,
+ -- Rate of event accesses in a section (i.e. count / dur).
+ value / (lead(ts) OVER (PARTITION BY track_id ORDER BY ts) - ts) AS access_rate
+FROM counter AS c
+JOIN counter_track AS t
+ ON c.track_id = t.id
+WHERE t.name = $event;
+
+-- The rate of L3 misses for each time slice based on the ARM DSU PMU counter's
+-- bus_access event. Units will be in number of L3 misses per ns. The number of
+-- accesses in a given duration can be calculated by multiplying the appropriate
+-- rate with the time in the window of interest.
+CREATE PERFETTO TABLE _arm_l3_miss_rate
+AS
+SELECT
+ ts, dur, access_rate AS l3_miss_rate
+FROM _get_rate("arm_dsu_0/bus_access/_cpu0");
+
+-- The rate of L3 accesses for each time slice based on the ARM DSU PMU
+-- counter's l3d_cache event. Units will be in number of DDR accesses per ns.
+-- The number of accesses in a given duration can be calculated by multiplying
+-- the appropriate rate with the time in the window of interest.
+CREATE PERFETTO TABLE _arm_l3_hit_rate
+AS
+SELECT
+ ts, dur, access_rate AS l3_hit_rate
+FROM _get_rate("arm_dsu_0/l3d_cache/_cpu0");
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql
new file mode 100644
index 0000000..93738f6
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql
@@ -0,0 +1,92 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed 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
+--
+-- https://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.
+
+INCLUDE PERFETTO MODULE cpu.freq;
+
+-- Filters for CPU specific frequency slices
+CREATE PERFETTO FUNCTION _per_cpu_freq_slice(cpu_match INT)
+RETURNS TABLE(ts LONG, dur INT, freq INT)
+AS
+SELECT ts, dur, freq
+FROM cpu_freq_counters WHERE cpu = $cpu_match;
+
+-- _freq_slices_cpux has CPUx specific frequency slices.
+CREATE PERFETTO TABLE _freq_slices_cpu0
+AS
+SELECT ts, dur, freq AS freq_0 FROM _per_cpu_freq_slice(0);
+
+CREATE PERFETTO TABLE _freq_slices_cpu1
+AS
+SELECT ts, dur, freq AS freq_1 FROM _per_cpu_freq_slice(1);
+
+CREATE PERFETTO TABLE _freq_slices_cpu2
+AS
+SELECT ts, dur, freq AS freq_2 FROM _per_cpu_freq_slice(2);
+
+CREATE PERFETTO TABLE _freq_slices_cpu3
+AS
+SELECT ts, dur, freq AS freq_3 FROM _per_cpu_freq_slice(3);
+
+CREATE PERFETTO TABLE _freq_slices_cpu4
+AS
+SELECT ts, dur, freq AS freq_4 FROM _per_cpu_freq_slice(4);
+
+CREATE PERFETTO TABLE _freq_slices_cpu5
+AS
+SELECT ts, dur, freq AS freq_5 FROM _per_cpu_freq_slice(5);
+
+CREATE PERFETTO TABLE _freq_slices_cpu6
+AS
+SELECT ts, dur, freq AS freq_6 FROM _per_cpu_freq_slice(6);
+
+CREATE PERFETTO TABLE _freq_slices_cpu7
+AS
+SELECT ts, dur, freq AS freq_7 FROM _per_cpu_freq_slice(7);
+
+-- SPAN_OUTER_JOIN of all CPUs' frequency tables.
+CREATE VIRTUAL TABLE _freq_slices_cpu01
+USING
+ SPAN_OUTER_JOIN(_freq_slices_cpu0, _freq_slices_cpu1);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu012
+USING
+ SPAN_OUTER_JOIN(_freq_slices_cpu01, _freq_slices_cpu2);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu0123
+USING
+ SPAN_OUTER_JOIN(_freq_slices_cpu012, _freq_slices_cpu3);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu01234
+USING
+ SPAN_OUTER_JOIN(_freq_slices_cpu0123, _freq_slices_cpu4);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu012345
+USING
+ SPAN_OUTER_JOIN(_freq_slices_cpu01234, _freq_slices_cpu5);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu0123456
+USING
+ SPAN_OUTER_JOIN(_freq_slices_cpu012345, _freq_slices_cpu6);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu01234567
+USING
+ SPAN_OUTER_JOIN(_freq_slices_cpu0123456, _freq_slices_cpu7);
+
+-- Table that holds time slices of the trace with the frequency transition
+-- information of every CPU in the system.
+CREATE PERFETTO TABLE _cpu_freq_all
+AS
+SELECT * FROM _freq_slices_cpu01234567;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql
new file mode 100644
index 0000000..006c876
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql
@@ -0,0 +1,160 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed 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
+--
+-- https://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.
+
+INCLUDE PERFETTO MODULE android.device;
+INCLUDE PERFETTO MODULE cpu.idle;
+
+-- Device specific info for deep idle time offsets
+CREATE PERFETTO TABLE _device_cpu_deep_idle_offsets
+AS
+WITH data(device, cpu, offset_ns) AS (
+ VALUES
+ ("oriole", 6, 200000),
+ ("oriole", 7, 200000),
+ ("raven", 6, 200000),
+ ("raven", 7, 200000),
+ ("eos", 0, 450000),
+ ("eos", 1, 450000),
+ ("eos", 2, 450000),
+ ("eos", 3, 450000)
+)
+select * from data;
+
+-- Get the corresponding deep idle time offset based on device and CPU.
+CREATE PERFETTO FUNCTION _get_deep_idle_offset(cpu INT)
+RETURNS INT
+AS
+SELECT offset_ns
+FROM _device_cpu_deep_idle_offsets as offsets, android_device_name as device
+WHERE
+ offsets.device = device.name AND cpu = $cpu;
+
+-- Adjust duration of active portion to be slightly longer to account for
+-- overhead cost of transitioning out of deep idle. This is done because the
+-- device is active and consumes power for longer than the logs actually report.
+CREATE PERFETTO FUNCTION _adjust_deep_idle(cpu_match INT)
+RETURNS TABLE(ts LONG, dur INT, idle INT) AS
+WITH
+ idle_prev AS (
+ SELECT
+ ts,
+ dur,
+ idle,
+ lag(idle) OVER (PARTITION BY track_id ORDER BY ts) AS idle_prev,
+ cpu
+ FROM cpu_idle_counters
+ ),
+ offset_ns AS (
+ SELECT IFNULL(_get_deep_idle_offset($cpu_match), 0) as offset_ns
+ ),
+ -- Adjusted ts if applicable, which makes the current deep idle state
+ -- slightly shorter.
+ idle_mod AS (
+ SELECT
+ IIF(
+ idle_prev = -1 AND idle = 1,
+ IIF(dur > offset_ns, ts + offset_ns, ts + dur),
+ ts
+ ) as ts,
+ -- ts_next is the starting timestamp of the next slice (e.g. end ts of
+ -- current slice)
+ ts + dur as ts_next,
+ idle
+ FROM idle_prev, offset_ns
+ WHERE cpu = $cpu_match
+ )
+SELECT
+ ts,
+ lead(ts, 1, trace_end()) OVER (ORDER by ts) - ts as dur,
+ idle
+FROM idle_mod
+WHERE ts != ts_next;
+
+-- idle_slices_cpux has CPUx specific idle state slices.
+CREATE PERFETTO TABLE _idle_slices_cpu0
+AS
+SELECT idle as idle_0, ts, dur
+FROM _adjust_deep_idle(0);
+
+CREATE PERFETTO TABLE _idle_slices_cpu1
+AS
+SELECT idle as idle_1, ts, dur
+FROM _adjust_deep_idle(1);
+
+CREATE PERFETTO TABLE _idle_slices_cpu2
+AS
+SELECT idle as idle_2, ts, dur
+FROM _adjust_deep_idle(2);
+
+CREATE PERFETTO TABLE _idle_slices_cpu3
+AS
+SELECT idle as idle_3, ts, dur
+FROM _adjust_deep_idle(3);
+
+CREATE PERFETTO TABLE _idle_slices_cpu4
+AS
+SELECT idle as idle_4, ts, dur
+FROM _adjust_deep_idle(4);
+
+CREATE PERFETTO TABLE _idle_slices_cpu5
+AS
+SELECT idle as idle_5, ts, dur
+FROM _adjust_deep_idle(5);
+
+CREATE PERFETTO TABLE _idle_slices_cpu6
+AS
+SELECT idle as idle_6, ts, dur
+FROM _adjust_deep_idle(6);
+
+CREATE PERFETTO TABLE _idle_slices_cpu7
+AS
+SELECT idle as idle_7, ts, dur
+FROM _adjust_deep_idle(7);
+
+-- SPAN_OUTER_JOIN of all CPUs' idle state tables.
+CREATE VIRTUAL TABLE _idle_slices_cpu01
+USING
+ SPAN_OUTER_JOIN(_idle_slices_cpu0, _idle_slices_cpu1);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu012
+USING
+ SPAN_OUTER_JOIN(_idle_slices_cpu01, _idle_slices_cpu2);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu0123
+USING
+ SPAN_OUTER_JOIN(_idle_slices_cpu012, _idle_slices_cpu3);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu01234
+USING
+ SPAN_OUTER_JOIN(_idle_slices_cpu0123, _idle_slices_cpu4);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu012345
+USING
+ SPAN_OUTER_JOIN(_idle_slices_cpu01234, _idle_slices_cpu5);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu0123456
+USING
+ SPAN_OUTER_JOIN(_idle_slices_cpu012345, _idle_slices_cpu6);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu01234567
+USING
+ SPAN_OUTER_JOIN(_idle_slices_cpu0123456, _idle_slices_cpu7);
+
+-- Table that holds time slices of the entire trace with the idle state
+-- transition information of every CPU in the system.
+CREATE PERFETTO TABLE _cpu_idle_all
+AS
+SELECT * FROM _idle_slices_cpu01234567;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql
new file mode 100644
index 0000000..63eeb7a
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql
@@ -0,0 +1,129 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed 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
+--
+-- https://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.
+
+INCLUDE PERFETTO MODULE time.conversion;
+INCLUDE PERFETTO MODULE wattson.arm_dsu;
+INCLUDE PERFETTO MODULE wattson.cpu_freq;
+INCLUDE PERFETTO MODULE wattson.cpu_idle;
+
+-- Combines idle and freq tables of all CPUs to create system state.
+CREATE VIRTUAL TABLE _idle_freq_slice
+USING
+ SPAN_OUTER_JOIN(_cpu_freq_all, _cpu_idle_all);
+
+-- get suspend resume state as logged by ftrace.
+CREATE PERFETTO TABLE _suspend_slice
+AS
+SELECT
+ ts, dur, TRUE AS suspended
+FROM slice
+WHERE name GLOB "timekeeping_freeze(0)";
+
+-- Combine suspend information with CPU idle and frequency system states.
+CREATE VIRTUAL TABLE _idle_freq_suspend_slice
+USING
+ SPAN_OUTER_JOIN(_idle_freq_slice, _suspend_slice);
+
+-- Add extra column indicating that idle and frequency info are present before
+-- SPAN_OUTER_JOIN with the DSU PMU counters.
+CREATE PERFETTO TABLE _idle_freq_filtered
+AS
+SELECT *, TRUE AS has_idle_freq
+FROM _idle_freq_suspend_slice
+WHERE freq_0 GLOB '*[0-9]*';
+
+-- Combine system state so that it has idle, freq, and L3 hit info.
+CREATE VIRTUAL TABLE _idle_freq_l3_hit_slice
+USING
+ SPAN_OUTER_JOIN(_idle_freq_filtered, _arm_l3_hit_rate);
+
+-- Combine system state so that it has idle, freq, L3 hit, and L3 miss info.
+CREATE VIRTUAL TABLE _idle_freq_l3_hit_l3_miss_slice
+USING
+ SPAN_OUTER_JOIN(_idle_freq_l3_hit_slice, _arm_l3_miss_rate);
+
+-- The final system state for the CPU subsystem, which has all the information
+-- needed by Wattson to estimate energy for the CPU subsystem.
+CREATE PERFETTO TABLE wattson_system_states(
+ -- Starting timestamp of the current counter where system state is constant.
+ ts LONG,
+ -- Duration of the current counter where system state is constant.
+ dur INT,
+ -- Number of L3 hits the current system state.
+ l3_hit_count INT,
+ -- Number of L3 misses in the current system state.
+ l3_miss_count INT,
+ -- Frequency of CPU0.
+ freq_0 INT,
+ -- Idle state of CPU0.
+ idle_0 INT,
+ -- Frequency of CPU1.
+ freq_1 INT,
+ -- Idle state of CPU1.
+ idle_1 INT,
+ -- Frequency of CPU2.
+ freq_2 INT,
+ -- Idle state of CPU2.
+ idle_2 INT,
+ -- Frequency of CPU3.
+ freq_3 INT,
+ -- Idle state of CPU3.
+ idle_3 INT,
+ -- Frequency of CPU4.
+ freq_4 INT,
+ -- Idle state of CPU4.
+ idle_4 INT,
+ -- Frequency of CPU5.
+ freq_5 INT,
+ -- Idle state of CPU5.
+ idle_5 INT,
+ -- Frequency of CPU6.
+ freq_6 INT,
+ -- Idle state of CPU6.
+ idle_6 INT,
+ -- Frequency of CPU7.
+ freq_7 INT,
+ -- Idle state of CPU7.
+ idle_7 INT,
+ -- Flag indicating if current system state is suspended.
+ suspended BOOL
+)
+AS
+SELECT
+ ts,
+ dur,
+ cast_int!(round(l3_hit_rate * dur, 0)) as l3_hit_count,
+ cast_int!(round(l3_miss_rate * dur, 0)) as l3_miss_count,
+ freq_0,
+ idle_0,
+ freq_1,
+ idle_1,
+ freq_2,
+ idle_2,
+ freq_3,
+ idle_3,
+ freq_4,
+ idle_4,
+ freq_5,
+ idle_5,
+ freq_6,
+ idle_6,
+ freq_7,
+ idle_7,
+ IFNULL(suspended, FALSE) as suspended
+FROM _idle_freq_l3_hit_l3_miss_slice
+-- Needs to be at least 1us to reduce inconsequential rows.
+WHERE dur > time_from_us(1) and has_idle_freq IS NOT NULL;
+
diff --git a/src/trace_processor/sqlite/BUILD.gn b/src/trace_processor/sqlite/BUILD.gn
index 8f553c1..902c9d8 100644
--- a/src/trace_processor/sqlite/BUILD.gn
+++ b/src/trace_processor/sqlite/BUILD.gn
@@ -21,7 +21,6 @@
"db_sqlite_table.cc",
"db_sqlite_table.h",
"module_lifecycle_manager.h",
- "query_cache.h",
"scoped_db.h",
"sql_source.cc",
"sql_source.h",
@@ -29,8 +28,6 @@
"sql_stats_table.h",
"sqlite_engine.cc",
"sqlite_engine.h",
- "sqlite_table.cc",
- "sqlite_table.h",
"sqlite_tokenizer.cc",
"sqlite_tokenizer.h",
"sqlite_utils.cc",
@@ -40,7 +37,6 @@
"stats_table.h",
]
deps = [
- ":query_constraints",
"..:metatrace",
"../../../gn:default_deps",
"../../../gn:sqlite",
@@ -64,29 +60,15 @@
public_deps = [ "bindings" ]
}
-source_set("query_constraints") {
- sources = [
- "query_constraints.cc",
- "query_constraints.h",
- ]
- deps = [
- "../../../gn:default_deps",
- "../../../gn:sqlite",
- "../../base",
- ]
-}
-
perfetto_unittest_source_set("unittests") {
testonly = true
sources = [
"db_sqlite_table_unittest.cc",
- "query_constraints_unittest.cc",
"sql_source_unittest.cc",
"sqlite_tokenizer_unittest.cc",
"sqlite_utils_unittest.cc",
]
deps = [
- ":query_constraints",
":sqlite",
"../../../gn:default_deps",
"../../../gn:gtest_and_gmock",
@@ -94,6 +76,7 @@
"../../../include/perfetto/trace_processor:basic_types",
"../../base",
"../../base:test_support",
+ "../db",
]
}
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index 8c1d9a0..eb61280 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -21,42 +21,39 @@
#include <cmath>
#include <cstddef>
#include <cstdint>
-#include <functional>
#include <iterator>
-#include <limits>
#include <memory>
+#include <numeric>
#include <optional>
#include <string>
#include <utility>
#include <vector>
+#include "perfetto/base/compiler.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/small_vector.h"
#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/string_splitter.h"
+#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
+#include "perfetto/public/compiler.h"
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/containers/row_map.h"
#include "src/trace_processor/db/column/types.h"
#include "src/trace_processor/db/runtime_table.h"
#include "src/trace_processor/db/table.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
-#include "src/trace_processor/sqlite/query_cache.h"
-#include "src/trace_processor/sqlite/query_constraints.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
#include "src/trace_processor/tp_metatrace.h"
#include "src/trace_processor/util/regex.h"
-#include "src/trace_processor/util/status_macros.h"
#include "protos/perfetto/trace_processor/metatrace_categories.pbzero.h"
namespace perfetto::trace_processor {
namespace {
-static constexpr uint32_t kInvalidArgumentIndex =
- std::numeric_limits<uint32_t>::max();
-
std::optional<FilterOp> SqliteOpToFilterOp(int sqlite_op) {
switch (sqlite_op) {
case SQLITE_INDEX_CONSTRAINT_EQ:
@@ -94,37 +91,6 @@
}
}
-SqlValue SqliteValueToSqlValue(sqlite3_value* sqlite_val) {
- auto col_type = sqlite3_value_type(sqlite_val);
- SqlValue value;
- switch (col_type) {
- case SQLITE_INTEGER:
- value.type = SqlValue::kLong;
- value.long_value = sqlite3_value_int64(sqlite_val);
- break;
- case SQLITE_TEXT:
- value.type = SqlValue::kString;
- value.string_value =
- reinterpret_cast<const char*>(sqlite3_value_text(sqlite_val));
- break;
- case SQLITE_FLOAT:
- value.type = SqlValue::kDouble;
- value.double_value = sqlite3_value_double(sqlite_val);
- break;
- case SQLITE_BLOB:
- value.type = SqlValue::kBytes;
- value.bytes_value = sqlite3_value_blob(sqlite_val);
- value.bytes_count = static_cast<size_t>(sqlite3_value_bytes(sqlite_val));
- break;
- case SQLITE_NULL:
- value.type = SqlValue::kNull;
- break;
- default:
- PERFETTO_FATAL("Unexpected sqlite3_value type");
- }
- return value;
-}
-
class SafeStringWriter {
public:
void AppendString(const char* s) {
@@ -140,83 +106,23 @@
}
base::StringView GetStringView() const {
- return base::StringView(buffer_.data(), buffer_.size());
+ return {buffer_.data(), buffer_.size()};
}
private:
base::SmallVector<char, 2048> buffer_;
};
-base::Status ValidateTableFunctionArguments(const std::string& name,
- const Table::Schema& schema,
- const QueryConstraints& qc) {
- for (uint32_t i = 0; i < schema.columns.size(); ++i) {
- if (!schema.columns[i].is_hidden) {
- continue;
+std::string CreateTableStatementFromSchema(const Table::Schema& schema,
+ const char* table_name) {
+ std::string stmt = "CREATE TABLE x(";
+ for (const auto& col : schema.columns) {
+ std::string c =
+ col.name + " " + sqlite::utils::SqlValueTypeToString(col.type);
+ if (col.is_hidden) {
+ c += " HIDDEN";
}
- auto pred = [i](const QueryConstraints::Constraint& c) {
- return i == static_cast<uint32_t>(c.column);
- };
- auto it =
- std::find_if(qc.constraints().begin(), qc.constraints().end(), pred);
- if (it == qc.constraints().end()) {
- return base::ErrStatus(
- "Failed to find constraint on column '%u' in function %s", i,
- name.c_str());
- }
-
- // Arguments should always use equality constraints.
- if (it->op != SQLITE_INDEX_CONSTRAINT_EQ) {
- return base::ErrStatus(
- "Only equality constraints supported on column '%u'", i);
- }
-
- // Disallow multiple constraints on an argument column.
- auto count = std::count_if(it + 1, qc.constraints().end(), pred);
- if (count > 0) {
- return base::ErrStatus(
- "Found multiple constraints on column '%u' in function %s", i,
- name.c_str());
- }
- }
- return base::OkStatus();
-}
-
-} // namespace
-
-DbSqliteTable::DbSqliteTable(sqlite3*, Context* context) : context_(context) {}
-DbSqliteTable::~DbSqliteTable() {
- if (context_->computation == DbSqliteTableContext::Computation::kRuntime) {
- context_->erase_runtime_table(name());
- }
-}
-
-base::Status DbSqliteTable::Init(int, const char* const*, Schema* schema) {
- switch (context_->computation) {
- case TableComputation::kStatic:
- schema_ = context_->static_schema;
- break;
- case TableComputation::kRuntime:
- runtime_table_ = context_->get_runtime_table(name());
- PERFETTO_CHECK(runtime_table_);
- PERFETTO_CHECK(!runtime_table_->columns().empty());
- schema_ = runtime_table_->schema();
- break;
- case TableComputation::kTableFunction:
- schema_ = context_->static_table_function->CreateSchema();
- break;
- }
- *schema = ComputeSchema(schema_, name().c_str());
- return base::OkStatus();
-}
-
-SqliteTableLegacy::Schema DbSqliteTable::ComputeSchema(
- const Table::Schema& schema,
- const char* table_name) {
- std::vector<SqliteTableLegacy::Column> schema_cols;
- for (uint32_t i = 0; i < schema.columns.size(); ++i) {
- const auto& col = schema.columns[i];
- schema_cols.emplace_back(i, col.name, col.type, col.is_hidden);
+ stmt += c + ",";
}
auto it =
@@ -227,418 +133,118 @@
"id column not found in %s. All tables need to contain an id column;",
table_name);
}
-
- std::vector<size_t> primary_keys;
- primary_keys.emplace_back(std::distance(schema.columns.begin(), it));
- return Schema(std::move(schema_cols), std::move(primary_keys));
+ stmt += "PRIMARY KEY(" + it->name + ")";
+ stmt += ") WITHOUT ROWID;";
+ return stmt;
}
-int DbSqliteTable::BestIndex(const QueryConstraints& qc, BestIndexInfo* info) {
- switch (context_->computation) {
- case TableComputation::kStatic:
- BestIndex(schema_, context_->static_table->row_count(), qc, info);
- break;
- case TableComputation::kRuntime:
- BestIndex(schema_, runtime_table_->row_count(), qc, info);
- break;
- case TableComputation::kTableFunction:
- base::Status status = ValidateTableFunctionArguments(name(), schema_, qc);
- if (!status.ok()) {
- // TODO(lalitm): instead of returning SQLITE_CONSTRAINT which shows the
- // user a very cryptic error message, consider instead SQLITE_OK but
- // with a very high (~infinite) cost. If SQLite still chose the query
- // plan after that, we can throw a proper error message in xFilter.
- return SQLITE_CONSTRAINT;
+base::StatusOr<SqlValue> SqliteValueToSqlValueChecked(sqlite3_value* value,
+ const Constraint& cs) {
+ SqlValue v = sqlite::utils::SqliteValueToSqlValue(value);
+ if constexpr (regex::IsRegexSupported()) {
+ if (cs.op == FilterOp::kRegex) {
+ if (cs.value.type != SqlValue::kString) {
+ return base::ErrStatus("Value has to be a string");
}
- BestIndex(schema_, context_->static_table_function->EstimateRowCount(),
- qc, info);
- break;
+ if (auto st = regex::Regex::Create(cs.value.AsString()); !st.ok()) {
+ return st.status();
+ }
+ }
+ }
+ return v;
+}
+
+int UpdateConstraintsAndOrderByFromIndex(DbSqliteModule::Cursor* c,
+ const char* idx_str,
+ sqlite3_value** argv) {
+ base::StringSplitter splitter(idx_str, ',');
+ PERFETTO_CHECK(splitter.Next());
+ PERFETTO_DCHECK(splitter.cur_token_size() >= 2);
+ PERFETTO_DCHECK(splitter.cur_token()[0] == 'C');
+
+ uint32_t cs_count = *base::CStringToUInt32(splitter.cur_token() + 1);
+
+ // We reuse this vector to reduce memory allocations on nested subqueries.
+ uint32_t c_offset = 0;
+ c->constraints.resize(cs_count);
+ for (auto& cs : c->constraints) {
+ PERFETTO_CHECK(splitter.Next());
+ cs.col_idx = *base::CStringToUInt32(splitter.cur_token());
+ PERFETTO_CHECK(splitter.Next());
+ cs.op = static_cast<FilterOp>(*base::CStringToUInt32(splitter.cur_token()));
+
+ auto value_or = SqliteValueToSqlValueChecked(argv[c_offset++], cs);
+ if (!value_or.ok()) {
+ return sqlite::utils::SetError(c->pVtab, value_or.status().c_message());
+ }
+ cs.value = *value_or;
+ }
+
+ PERFETTO_CHECK(splitter.Next());
+ PERFETTO_DCHECK(splitter.cur_token_size() >= 2);
+ PERFETTO_DCHECK(splitter.cur_token()[0] == 'O');
+
+ uint32_t ob_count = *base::CStringToUInt32(splitter.cur_token() + 1);
+
+ // We reuse this vector to reduce memory allocations on nested subqueries.
+ c->orders.resize(ob_count);
+ for (auto& ob : c->orders) {
+ PERFETTO_CHECK(splitter.Next());
+ ob.col_idx = *base::CStringToUInt32(splitter.cur_token());
+ PERFETTO_CHECK(splitter.Next());
+ ob.desc = *base::CStringToUInt32(splitter.cur_token());
}
return SQLITE_OK;
}
-void DbSqliteTable::BestIndex(const Table::Schema& schema,
- uint32_t row_count,
- const QueryConstraints& qc,
- BestIndexInfo* info) {
- auto cost_and_rows = EstimateCost(schema, row_count, qc);
- info->estimated_cost = cost_and_rows.cost;
- info->estimated_rows = cost_and_rows.rows;
-
- const auto& cs = qc.constraints();
- for (uint32_t i = 0; i < cs.size(); ++i) {
- // SqliteOpToFilterOp will return std::nullopt for any constraint which we
- // don't support filtering ourselves. Only omit filtering by SQLite when we
- // can handle filtering.
- std::optional<FilterOp> opt_op = SqliteOpToFilterOp(cs[i].op);
- info->sqlite_omit_constraint[i] = opt_op.has_value();
- }
-
- // We can sort on any column correctly.
- info->sqlite_omit_order_by = true;
-}
-
-base::Status DbSqliteTable::ModifyConstraints(QueryConstraints* qc) {
- ModifyConstraints(schema_, qc);
- return base::OkStatus();
-}
-
-void DbSqliteTable::ModifyConstraints(const Table::Schema& schema,
- QueryConstraints* qc) {
- using C = QueryConstraints::Constraint;
-
- // Reorder constraints to consider the constraints on columns which are
- // cheaper to filter first.
- auto* cs = qc->mutable_constraints();
- std::sort(cs->begin(), cs->end(), [&schema](const C& a, const C& b) {
- uint32_t a_idx = static_cast<uint32_t>(a.column);
- uint32_t b_idx = static_cast<uint32_t>(b.column);
- const auto& a_col = schema.columns[a_idx];
- const auto& b_col = schema.columns[b_idx];
-
- // Id columns are always very cheap to filter on so try and get them
- // first.
- if (a_col.is_id || b_col.is_id)
- return a_col.is_id && !b_col.is_id;
-
- // Set id columns are always very cheap to filter on so try and get them
- // second.
- if (a_col.is_set_id || b_col.is_set_id)
- return a_col.is_set_id && !b_col.is_set_id;
-
- // Sorted columns are also quite cheap to filter so order them after
- // any id/set id columns.
- if (a_col.is_sorted || b_col.is_sorted)
- return a_col.is_sorted && !b_col.is_sorted;
-
- // TODO(lalitm): introduce more orderings here based on empirical data.
- return false;
- });
-
- // Remove any order by constraints which also have an equality constraint.
- auto* ob = qc->mutable_order_by();
- {
- auto p = [&cs](const QueryConstraints::OrderBy& o) {
- auto inner_p = [&o](const QueryConstraints::Constraint& c) {
- return c.column == o.iColumn && sqlite::utils::IsOpEq(c.op);
- };
- return std::any_of(cs->begin(), cs->end(), inner_p);
- };
- auto remove_it = std::remove_if(ob->begin(), ob->end(), p);
- ob->erase(remove_it, ob->end());
- }
-
- // Go through the order by constraints in reverse order and eliminate
- // constraints until the first non-sorted column or the first order by in
- // descending order.
- {
- auto p = [&schema](const QueryConstraints::OrderBy& o) {
- const auto& col = schema.columns[static_cast<uint32_t>(o.iColumn)];
- return o.desc || !col.is_sorted;
- };
- auto first_non_sorted_it = std::find_if(ob->rbegin(), ob->rend(), p);
- auto pop_count = std::distance(ob->rbegin(), first_non_sorted_it);
- ob->resize(ob->size() - static_cast<uint32_t>(pop_count));
- }
-}
-
-DbSqliteTable::QueryCost DbSqliteTable::EstimateCost(
+PERFETTO_ALWAYS_INLINE void TryCacheCreateSortedTable(
+ DbSqliteModule::Cursor* cursor,
const Table::Schema& schema,
- uint32_t row_count,
- const QueryConstraints& qc) {
- // Currently our cost estimation algorithm is quite simplistic but is good
- // enough for the simplest cases.
- // TODO(lalitm): replace hardcoded constants with either more heuristics
- // based on the exact type of constraint or profiling the queries themselves.
-
- // We estimate the fixed cost of set-up and tear-down of a query in terms of
- // the number of rows scanned.
- constexpr double kFixedQueryCost = 1000.0;
-
- // Setup the variables for estimating the number of rows we will have at the
- // end of filtering. Note that |current_row_count| should always be at least 1
- // unless we are absolutely certain that we will return no rows as otherwise
- // SQLite can make some bad choices.
- uint32_t current_row_count = row_count;
-
- // If the table is empty, any constraint set only pays the fixed cost. Also we
- // can return 0 as the row count as we are certain that we will return no
- // rows.
- if (current_row_count == 0)
- return QueryCost{kFixedQueryCost, 0};
-
- // Setup the variables for estimating the cost of filtering.
- double filter_cost = 0.0;
- const auto& cs = qc.constraints();
- for (const auto& c : cs) {
- if (current_row_count < 2)
- break;
- const auto& col_schema = schema.columns[static_cast<uint32_t>(c.column)];
- if (sqlite::utils::IsOpEq(c.op) && col_schema.is_id) {
- // If we have an id equality constraint, we can very efficiently filter
- // down to a single row in C++. However, if we're joining with another
- // table, SQLite will do this once per row which can be extremely
- // expensive because of all the virtual table (which is implemented using
- // virtual function calls) machinery. Indicate this by saying that an
- // entire filter call is ~10x the cost of iterating a single row.
- filter_cost += 10;
- current_row_count = 1;
- } else if (sqlite::utils::IsOpEq(c.op)) {
- // If there is only a single equality constraint, we have special logic
- // to sort by that column and then binary search if we see the constraint
- // set often. Model this by dividing by the log of the number of rows as
- // a good approximation. Otherwise, we'll need to do a full table scan.
- // Alternatively, if the column is sorted, we can use the same binary
- // search logic so we have the same low cost (even better because we don't
- // have to sort at all).
- filter_cost += cs.size() == 1 || col_schema.is_sorted
- ? log2(current_row_count)
- : current_row_count;
-
- // As an extremely rough heuristic, assume that an equalty constraint will
- // cut down the number of rows by approximately double log of the number
- // of rows.
- double estimated_rows = current_row_count / (2 * log2(current_row_count));
- current_row_count = std::max(static_cast<uint32_t>(estimated_rows), 1u);
- } else if (col_schema.is_sorted &&
- (sqlite::utils::IsOpLe(c.op) || sqlite::utils::IsOpLt(c.op) ||
- sqlite::utils::IsOpGt(c.op) || sqlite::utils::IsOpGe(c.op))) {
- // On a sorted column, if we see any partition constraints, we can do this
- // filter very efficiently. Model this using the log of the number of
- // rows as a good approximation.
- filter_cost += log2(current_row_count);
-
- // As an extremely rough heuristic, assume that an partition constraint
- // will cut down the number of rows by approximately double log of the
- // number of rows.
- double estimated_rows = current_row_count / (2 * log2(current_row_count));
- current_row_count = std::max(static_cast<uint32_t>(estimated_rows), 1u);
- } else {
- // Otherwise, we will need to do a full table scan and we estimate we will
- // maybe (at best) halve the number of rows.
- filter_cost += current_row_count;
- current_row_count = std::max(current_row_count / 2u, 1u);
- }
- }
-
- // Now, to figure out the cost of sorting, multiply the final row count
- // by |qc.order_by().size()| * log(row count). This should act as a crude
- // estimation of the cost.
- double sort_cost =
- static_cast<double>(qc.order_by().size() * current_row_count) *
- log2(current_row_count);
-
- // The cost of iterating rows is more expensive than just filtering the rows
- // so multiply by an appropriate factor.
- double iteration_cost = current_row_count * 2.0;
-
- // To get the final cost, add up all the individual components.
- double final_cost =
- kFixedQueryCost + filter_cost + sort_cost + iteration_cost;
- return QueryCost{final_cost, current_row_count};
-}
-
-std::unique_ptr<SqliteTableLegacy::BaseCursor> DbSqliteTable::CreateCursor() {
- return std::make_unique<Cursor>(this, context_->cache);
-}
-
-DbSqliteTable::Cursor::Cursor(DbSqliteTable* sqlite_table, QueryCache* cache)
- : SqliteTableLegacy::BaseCursor(sqlite_table),
- db_sqlite_table_(sqlite_table),
- cache_(cache) {
- switch (db_sqlite_table_->context_->computation) {
- case TableComputation::kStatic:
- upstream_table_ = db_sqlite_table_->context_->static_table;
- argument_index_per_column_.resize(sqlite_table->schema_.columns.size(),
- kInvalidArgumentIndex);
- break;
- case TableComputation::kRuntime:
- upstream_table_ = db_sqlite_table_->runtime_table_;
- argument_index_per_column_.resize(sqlite_table->schema_.columns.size(),
- kInvalidArgumentIndex);
- break;
- case TableComputation::kTableFunction: {
- uint32_t argument_index = 0;
- for (const auto& col : sqlite_table->schema_.columns) {
- argument_index_per_column_.emplace_back(
- col.is_hidden ? argument_index++ : kInvalidArgumentIndex);
- }
- table_function_arguments_.resize(argument_index);
- break;
- }
- }
-}
-DbSqliteTable::Cursor::~Cursor() = default;
-
-void DbSqliteTable::Cursor::TryCacheCreateSortedTable(
- const QueryConstraints& qc,
- FilterHistory history) {
- // Check if we have a cache. Some subclasses (e.g. the flamegraph table) may
- // pass nullptr to disable caching.
- if (!cache_)
- return;
-
- if (history == FilterHistory::kDifferent) {
- repeated_cache_count_ = 0;
-
- // Check if the new constraint set is cached by another cursor.
- sorted_cache_table_ =
- cache_->GetIfCached(upstream_table_, qc.constraints());
+ bool is_same_idx) {
+ if (!is_same_idx) {
+ cursor->repeated_cache_count = 0;
return;
}
- PERFETTO_DCHECK(history == FilterHistory::kSame);
-
- // TODO(lalitm): all of the caching policy below should live in QueryCache and
- // not here. This is only here temporarily to allow migration of sched without
- // regressing UI performance and should be removed ASAP.
-
- // Only try and create the cached table on exactly the third time we see this
- // constraint set.
+ // Only try and create the cached table on exactly the third time we see
+ // this constraint set.
constexpr uint32_t kRepeatedThreshold = 3;
- if (sorted_cache_table_ || repeated_cache_count_++ != kRepeatedThreshold)
+ if (cursor->sorted_cache_table ||
+ cursor->repeated_cache_count++ != kRepeatedThreshold) {
return;
+ }
// If we have more than one constraint, we can't cache the table using
// this method.
- if (qc.constraints().size() != 1)
+ if (cursor->constraints.size() != 1) {
return;
+ }
// If the constraing is not an equality constraint, there's little
// benefit to caching
- const auto& c = qc.constraints().front();
- if (!sqlite::utils::IsOpEq(c.op))
+ const auto& c = cursor->constraints.front();
+ if (c.op != FilterOp::kEq) {
return;
+ }
// If the column is already sorted, we don't need to cache at all.
- auto col = static_cast<uint32_t>(c.column);
- if (db_sqlite_table_->schema_.columns[col].is_sorted)
+ if (schema.columns[c.col_idx].is_sorted) {
return;
+ }
// Try again to get the result or start caching it.
- sorted_cache_table_ =
- cache_->GetOrCache(upstream_table_, qc.constraints(), [this, col]() {
- return upstream_table_->Sort({Order{col, false}});
- });
+ cursor->sorted_cache_table =
+ cursor->upstream_table->Sort({Order{c.col_idx, false}});
}
-base::Status DbSqliteTable::Cursor::Filter(const QueryConstraints& qc,
- sqlite3_value** argv,
- FilterHistory history) {
- // Clear out the iterator before filtering to ensure the destructor is run
- // before the table's destructor.
- iterator_ = std::nullopt;
-
- RETURN_IF_ERROR(PopulateConstraintsAndArguments(qc, argv));
- PopulateOrderBys(qc);
-
- // Setup the upstream table based on the computation state.
- switch (db_sqlite_table_->context_->computation) {
- case TableComputation::kStatic:
- case TableComputation::kRuntime:
- // Tries to create a sorted cached table which can be used to speed up
- // filters below.
- TryCacheCreateSortedTable(qc, history);
- break;
- case TableComputation::kTableFunction: {
- PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED,
- "TABLE_FUNCTION_CALL", [this](metatrace::Record* r) {
- r->AddArg("Name", db_sqlite_table_->name());
- });
- base::StatusOr<std::unique_ptr<Table>> table =
- db_sqlite_table_->context_->static_table_function->ComputeTable(
- table_function_arguments_);
- if (!table.ok()) {
- return base::ErrStatus("%s: %s", db_sqlite_table_->name().c_str(),
- table.status().c_message());
- }
- dynamic_table_ = std::move(*table);
- upstream_table_ = dynamic_table_.get();
- break;
- }
- }
-
- PERFETTO_TP_TRACE(
- metatrace::Category::QUERY_DETAILED, "DB_TABLE_FILTER_AND_SORT",
- [this](metatrace::Record* r) { FilterAndSortMetatrace(r); });
-
- RowMap filter_map = SourceTable()->QueryToRowMap(constraints_, orders_);
- if (filter_map.IsRange() && filter_map.size() <= 1) {
- // Currently, our criteria where we have a special fast path is if it's
- // a single ranged row. We have this fast path for joins on id columns
- // where we get repeated queries filtering down to a single row. The
- // other path performs allocations when creating the new table as well
- // as the iterator on the new table whereas this path only uses a single
- // number and lives entirely on the stack.
-
- // TODO(lalitm): investigate some other criteria where it is beneficial
- // to have a fast path and expand to them.
- mode_ = Mode::kSingleRow;
- single_row_ = filter_map.size() == 1 ? std::make_optional(filter_map.Get(0))
- : std::nullopt;
- eof_ = !single_row_.has_value();
- } else {
- mode_ = Mode::kTable;
- iterator_ = SourceTable()->ApplyAndIterateRows(std::move(filter_map));
- eof_ = !*iterator_;
- }
- return base::OkStatus();
-}
-
-base::Status DbSqliteTable::Cursor::PopulateConstraintsAndArguments(
- const QueryConstraints& qc,
- sqlite3_value** argv) {
- // We reuse this vector to reduce memory allocations on nested subqueries.
- constraints_.resize(qc.constraints().size());
- uint32_t constraints_pos = 0;
- for (size_t i = 0; i < qc.constraints().size(); ++i) {
- const auto& cs = qc.constraints()[i];
- auto col = static_cast<uint32_t>(cs.column);
-
- // If we get a std::nullopt FilterOp, that means we should allow SQLite
- // to handle the constraint.
- std::optional<FilterOp> opt_op = SqliteOpToFilterOp(cs.op);
- if (!opt_op)
- continue;
-
- SqlValue value = SqliteValueToSqlValue(argv[i]);
- if constexpr (regex::IsRegexSupported()) {
- if (*opt_op == FilterOp::kRegex) {
- if (value.type != SqlValue::kString)
- return base::ErrStatus("Value has to be a string");
-
- if (auto regex_status = regex::Regex::Create(value.AsString());
- !regex_status.ok())
- return regex_status.status();
- }
- }
-
- uint32_t argument_index = argument_index_per_column_[col];
- if (argument_index == kInvalidArgumentIndex) {
- constraints_[constraints_pos++] = Constraint{col, *opt_op, value};
- } else {
- table_function_arguments_[argument_index] = value;
- }
- }
- constraints_.resize(constraints_pos);
- return base::OkStatus();
-}
-
-void DbSqliteTable::Cursor::PopulateOrderBys(const QueryConstraints& qc) {
- // We reuse this vector to reduce memory allocations on nested subqueries.
- orders_.resize(qc.order_by().size());
- for (size_t i = 0; i < qc.order_by().size(); ++i) {
- const auto& ob = qc.order_by()[i];
- auto col = static_cast<uint32_t>(ob.iColumn);
- orders_[i] = Order{col, static_cast<bool>(ob.desc)};
- }
-}
-
-void DbSqliteTable::Cursor::FilterAndSortMetatrace(metatrace::Record* r) {
- r->AddArg("Table", db_sqlite_table_->name());
- for (const Constraint& c : constraints_) {
+void FilterAndSortMetatrace(const std::string& table_name,
+ const Table::Schema& schema,
+ DbSqliteModule::Cursor* cursor,
+ metatrace::Record* r) {
+ r->AddArg("Table", table_name);
+ for (const Constraint& c : cursor->constraints) {
SafeStringWriter writer;
- writer.AppendString(db_sqlite_table_->schema_.columns[c.col_idx].name);
+ writer.AppendString(schema.columns[c.col_idx].name);
writer.AppendString(" ");
switch (c.op) {
@@ -697,37 +303,517 @@
r->AddArg("Constraint", writer.GetStringView());
}
- for (const auto& o : orders_) {
+ for (const auto& o : cursor->orders) {
SafeStringWriter writer;
- writer.AppendString(db_sqlite_table_->schema_.columns[o.col_idx].name);
+ writer.AppendString(schema.columns[o.col_idx].name);
if (o.desc)
writer.AppendString(" desc");
r->AddArg("Order by", writer.GetStringView());
}
}
-DbSqliteTableContext::DbSqliteTableContext(QueryCache* query_cache,
- const Table* table,
- Table::Schema schema)
- : cache(query_cache),
- computation(Computation::kStatic),
- static_table(table),
- static_schema(std::move(schema)) {}
+} // namespace
-DbSqliteTableContext::DbSqliteTableContext(
- QueryCache* query_cache,
- std::function<RuntimeTable*(std::string)> get_table,
- std::function<void(std::string)> erase_table)
- : cache(query_cache),
- computation(Computation::kRuntime),
- get_runtime_table(std::move(get_table)),
- erase_runtime_table(std::move(erase_table)) {}
+int DbSqliteModule::Create(sqlite3* db,
+ void* ctx,
+ int argc,
+ const char* const* argv,
+ sqlite3_vtab** vtab,
+ char**) {
+ PERFETTO_CHECK(argc == 3);
+ auto* context = GetContext(ctx);
+ auto state = std::move(context->temporary_create_state);
+ PERFETTO_CHECK(state);
-DbSqliteTableContext::DbSqliteTableContext(
- QueryCache* query_cache,
- std::unique_ptr<StaticTableFunction> table)
- : cache(query_cache),
- computation(Computation::kTableFunction),
- static_table_function(std::move(table)) {}
+ std::string sql = CreateTableStatementFromSchema(state->schema, argv[2]);
+ if (int ret = sqlite3_declare_vtab(db, sql.c_str()); ret != SQLITE_OK) {
+ return ret;
+ }
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ res->state = context->manager.OnCreate(argv, std::move(state));
+ res->table_name = argv[2];
+ *vtab = res.release();
+ return SQLITE_OK;
+}
+
+int DbSqliteModule::Destroy(sqlite3_vtab* vtab) {
+ auto* t = GetVtab(vtab);
+ auto* s = sqlite::ModuleStateManager<DbSqliteModule>::GetState(t->state);
+ if (s->computation == TableComputation::kStatic) {
+ // SQLite does not read error messages returned from xDestroy so just pick
+ // the closest appropriate error code.
+ return SQLITE_READONLY;
+ }
+ std::unique_ptr<Vtab> tab(GetVtab(vtab));
+ sqlite::ModuleStateManager<DbSqliteModule>::OnDestroy(tab->state);
+ return SQLITE_OK;
+}
+
+int DbSqliteModule::Connect(sqlite3* db,
+ void* ctx,
+ int argc,
+ const char* const* argv,
+ sqlite3_vtab** vtab,
+ char**) {
+ PERFETTO_CHECK(argc == 3);
+ auto* context = GetContext(ctx);
+
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ res->state = context->manager.OnConnect(argv);
+ res->table_name = argv[2];
+
+ auto* state =
+ sqlite::ModuleStateManager<DbSqliteModule>::GetState(res->state);
+ std::string sql = CreateTableStatementFromSchema(state->schema, argv[2]);
+ if (int ret = sqlite3_declare_vtab(db, sql.c_str()); ret != SQLITE_OK) {
+ // If the registration happens to fail, make sure to disconnect the state
+ // again.
+ sqlite::ModuleStateManager<DbSqliteModule>::OnDisconnect(res->state);
+ return ret;
+ }
+ *vtab = res.release();
+ return SQLITE_OK;
+}
+
+int DbSqliteModule::Disconnect(sqlite3_vtab* vtab) {
+ std::unique_ptr<Vtab> tab(GetVtab(vtab));
+ sqlite::ModuleStateManager<DbSqliteModule>::OnDisconnect(tab->state);
+ return SQLITE_OK;
+}
+
+int DbSqliteModule::BestIndex(sqlite3_vtab* vtab, sqlite3_index_info* info) {
+ auto* t = GetVtab(vtab);
+ auto* s = sqlite::ModuleStateManager<DbSqliteModule>::GetState(t->state);
+
+ uint32_t row_count;
+ int argv_index;
+ switch (s->computation) {
+ case TableComputation::kStatic:
+ row_count = s->static_table->row_count();
+ argv_index = 1;
+ break;
+ case TableComputation::kRuntime:
+ row_count = s->runtime_table->row_count();
+ argv_index = 1;
+ break;
+ case TableComputation::kTableFunction:
+ base::Status status = sqlite::utils::ValidateFunctionArguments(
+ info, static_cast<size_t>(s->argument_count),
+ [s](uint32_t i) { return s->schema.columns[i].is_hidden; });
+ if (!status.ok()) {
+ // TODO(lalitm): instead of returning SQLITE_CONSTRAINT which shows the
+ // user a very cryptic error message, consider instead SQLITE_OK but
+ // with a very high (~infinite) cost. If SQLite still chose the query
+ // plan after that, we can throw a proper error message in xFilter.
+ return SQLITE_CONSTRAINT;
+ }
+ row_count = s->static_table_function->EstimateRowCount();
+ argv_index = 1 + s->argument_count;
+ break;
+ }
+
+ std::vector<int> cs_idxes;
+ cs_idxes.reserve(static_cast<uint32_t>(info->nConstraint));
+ for (int i = 0; i < info->nConstraint; ++i) {
+ const auto& c = info->aConstraint[i];
+ if (!c.usable || info->aConstraintUsage[i].omit) {
+ continue;
+ }
+ if (std::optional<FilterOp> opt_op = SqliteOpToFilterOp(c.op); !opt_op) {
+ continue;
+ }
+ cs_idxes.push_back(i);
+ }
+
+ std::vector<int> ob_idxes(static_cast<uint32_t>(info->nOrderBy));
+ std::iota(ob_idxes.begin(), ob_idxes.end(), 0);
+
+ // Reorder constraints to consider the constraints on columns which are
+ // cheaper to filter first.
+ {
+ std::sort(cs_idxes.begin(), cs_idxes.end(), [s, info](int a, int b) {
+ auto a_idx = static_cast<uint32_t>(info->aConstraint[a].iColumn);
+ auto b_idx = static_cast<uint32_t>(info->aConstraint[b].iColumn);
+ const auto& a_col = s->schema.columns[a_idx];
+ const auto& b_col = s->schema.columns[b_idx];
+
+ // Id columns are always very cheap to filter on so try and get them
+ // first.
+ if (a_col.is_id || b_col.is_id)
+ return a_col.is_id && !b_col.is_id;
+
+ // Set id columns are always very cheap to filter on so try and get them
+ // second.
+ if (a_col.is_set_id || b_col.is_set_id)
+ return a_col.is_set_id && !b_col.is_set_id;
+
+ // Sorted columns are also quite cheap to filter so order them after
+ // any id/set id columns.
+ if (a_col.is_sorted || b_col.is_sorted)
+ return a_col.is_sorted && !b_col.is_sorted;
+
+ // TODO(lalitm): introduce more orderings here based on empirical data.
+ return false;
+ });
+ }
+
+ // Remove any order by constraints which also have an equality constraint.
+ {
+ auto p = [info, &cs_idxes](int o_idx) {
+ auto& o = info->aOrderBy[o_idx];
+ auto inner_p = [info, &o](int c_idx) {
+ auto& c = info->aConstraint[c_idx];
+ return c.iColumn == o.iColumn && sqlite::utils::IsOpEq(c.op);
+ };
+ return std::any_of(cs_idxes.begin(), cs_idxes.end(), inner_p);
+ };
+ ob_idxes.erase(std::remove_if(ob_idxes.begin(), ob_idxes.end(), p),
+ ob_idxes.end());
+ }
+
+ // Go through the order by constraints in reverse order and eliminate
+ // constraints until the first non-sorted column or the first order by in
+ // descending order.
+ {
+ auto p = [info, s](int o_idx) {
+ auto& o = info->aOrderBy[o_idx];
+ const auto& col = s->schema.columns[static_cast<uint32_t>(o.iColumn)];
+ return o.desc || !col.is_sorted;
+ };
+ auto first_non_sorted_it =
+ std::find_if(ob_idxes.rbegin(), ob_idxes.rend(), p);
+ auto pop_count = std::distance(ob_idxes.rbegin(), first_non_sorted_it);
+ ob_idxes.resize(ob_idxes.size() - static_cast<uint32_t>(pop_count));
+ }
+
+ std::string cs_idx_str;
+ for (int i : cs_idxes) {
+ const auto& c = info->aConstraint[i];
+ auto& o = info->aConstraintUsage[i];
+ o.omit = true;
+ o.argvIndex = argv_index++;
+
+ auto op = SqliteOpToFilterOp(c.op);
+ PERFETTO_DCHECK(op);
+
+ cs_idx_str += ',';
+ cs_idx_str += std::to_string(c.iColumn);
+ cs_idx_str += ',';
+ cs_idx_str += std::to_string(static_cast<uint32_t>(*op));
+ }
+
+ std::string idx_str = "C";
+ idx_str += std::to_string(cs_idxes.size());
+ idx_str += cs_idx_str;
+ idx_str += ",";
+ idx_str += "O";
+ idx_str += std::to_string(ob_idxes.size());
+ for (int i : ob_idxes) {
+ idx_str += ',';
+ idx_str += std::to_string(info->aOrderBy[i].iColumn);
+ idx_str += ',';
+ idx_str += std::to_string(info->aOrderBy[i].desc);
+ }
+
+ info->idxNum = t->best_index_num++;
+ info->idxStr = sqlite3_mprintf("%s", idx_str.c_str());
+ info->needToFreeIdxStr = true;
+
+ // We can sort on any column correctly.
+ info->orderByConsumed = true;
+
+ auto cost_and_rows =
+ EstimateCost(s->schema, row_count, info, cs_idxes, ob_idxes);
+ info->estimatedCost = cost_and_rows.cost;
+ info->estimatedRows = cost_and_rows.rows;
+
+ return SQLITE_OK;
+}
+
+int DbSqliteModule::Open(sqlite3_vtab* tab, sqlite3_vtab_cursor** cursor) {
+ auto* t = GetVtab(tab);
+ auto* s = sqlite::ModuleStateManager<DbSqliteModule>::GetState(t->state);
+ std::unique_ptr<Cursor> c = std::make_unique<Cursor>();
+ switch (s->computation) {
+ case TableComputation::kStatic:
+ c->upstream_table = s->static_table;
+ break;
+ case TableComputation::kRuntime:
+ c->upstream_table = s->runtime_table.get();
+ break;
+ case TableComputation::kTableFunction:
+ c->table_function_arguments.resize(
+ static_cast<size_t>(s->argument_count));
+ break;
+ }
+ *cursor = c.release();
+ return SQLITE_OK;
+}
+
+int DbSqliteModule::Close(sqlite3_vtab_cursor* cursor) {
+ std::unique_ptr<Cursor> c(GetCursor(cursor));
+ return SQLITE_OK;
+}
+
+int DbSqliteModule::Filter(sqlite3_vtab_cursor* cursor,
+ int idx_num,
+ const char* idx_str,
+ int,
+ sqlite3_value** argv) {
+ auto* c = GetCursor(cursor);
+ auto* t = GetVtab(cursor->pVtab);
+ auto* s = sqlite::ModuleStateManager<DbSqliteModule>::GetState(t->state);
+
+ // Clear out the iterator before filtering to ensure the destructor is run
+ // before the table's destructor.
+ c->iterator = std::nullopt;
+
+ size_t offset = c->table_function_arguments.size();
+ bool is_same_idx = idx_num == c->last_idx_num;
+ if (PERFETTO_LIKELY(is_same_idx)) {
+ for (auto& cs : c->constraints) {
+ auto value_or = SqliteValueToSqlValueChecked(argv[offset++], cs);
+ if (!value_or.ok()) {
+ return sqlite::utils::SetError(c->pVtab, value_or.status().c_message());
+ }
+ cs.value = *value_or;
+ }
+ } else {
+ if (int r = UpdateConstraintsAndOrderByFromIndex(c, idx_str, argv + offset);
+ r != SQLITE_OK) {
+ return r;
+ }
+ c->last_idx_num = idx_num;
+ }
+
+ // Setup the upstream table based on the computation state.
+ switch (s->computation) {
+ case TableComputation::kStatic:
+ case TableComputation::kRuntime:
+ // Tries to create a sorted cached table which can be used to speed up
+ // filters below.
+ TryCacheCreateSortedTable(c, s->schema, is_same_idx);
+ break;
+ case TableComputation::kTableFunction: {
+ PERFETTO_TP_TRACE(
+ metatrace::Category::QUERY_DETAILED, "TABLE_FUNCTION_CALL",
+ [t](metatrace::Record* r) { r->AddArg("Name", t->table_name); });
+ for (uint32_t i = 0; i < c->table_function_arguments.size(); ++i) {
+ c->table_function_arguments[i] =
+ sqlite::utils::SqliteValueToSqlValue(argv[i]);
+ }
+ base::StatusOr<std::unique_ptr<Table>> table =
+ s->static_table_function->ComputeTable(c->table_function_arguments);
+ if (!table.ok()) {
+ base::StackString<1024> err("%s: %s", t->table_name.c_str(),
+ table.status().c_message());
+ return sqlite::utils::SetError(t, err.c_str());
+ }
+ c->dynamic_table = std::move(*table);
+ c->upstream_table = c->dynamic_table.get();
+ break;
+ }
+ }
+
+ PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED,
+ "DB_TABLE_FILTER_AND_SORT",
+ [s, t, c](metatrace::Record* r) {
+ FilterAndSortMetatrace(t->table_name, s->schema, c, r);
+ });
+
+ const auto* source_table =
+ c->sorted_cache_table ? &*c->sorted_cache_table : c->upstream_table;
+ RowMap filter_map = source_table->QueryToRowMap(c->constraints, c->orders);
+ if (filter_map.IsRange() && filter_map.size() <= 1) {
+ // Currently, our criteria where we have a special fast path is if it's
+ // a single ranged row. We have this fast path for joins on id columns
+ // where we get repeated queries filtering down to a single row. The
+ // other path performs allocations when creating the new table as well
+ // as the iterator on the new table whereas this path only uses a single
+ // number and lives entirely on the stack.
+
+ // TODO(lalitm): investigate some other criteria where it is beneficial
+ // to have a fast path and expand to them.
+ c->mode = Cursor::Mode::kSingleRow;
+ c->single_row = filter_map.size() == 1
+ ? std::make_optional(filter_map.Get(0))
+ : std::nullopt;
+ c->eof = !c->single_row.has_value();
+ } else {
+ c->mode = Cursor::Mode::kTable;
+ c->iterator = source_table->ApplyAndIterateRows(std::move(filter_map));
+ c->eof = !*c->iterator;
+ }
+ return SQLITE_OK;
+}
+
+int DbSqliteModule::Next(sqlite3_vtab_cursor* cursor) {
+ auto* c = GetCursor(cursor);
+ if (c->mode == Cursor::Mode::kSingleRow) {
+ c->eof = true;
+ } else {
+ c->eof = !++*c->iterator;
+ }
+ return SQLITE_OK;
+}
+
+int DbSqliteModule::Eof(sqlite3_vtab_cursor* cursor) {
+ return GetCursor(cursor)->eof;
+}
+
+int DbSqliteModule::Column(sqlite3_vtab_cursor* cursor,
+ sqlite3_context* ctx,
+ int N) {
+ Cursor* c = GetCursor(cursor);
+ auto idx = static_cast<uint32_t>(N);
+ const auto* source_table =
+ c->sorted_cache_table ? &*c->sorted_cache_table : c->upstream_table;
+ SqlValue value = c->mode == Cursor::Mode::kSingleRow
+ ? source_table->columns()[idx].Get(*c->single_row)
+ : c->iterator->Get(idx);
+ // We can say kSqliteStatic for strings because all strings are expected
+ // to come from the string pool. Thus they will be valid for the lifetime
+ // of trace processor. Similarily, for bytes, we can also use
+ // kSqliteStatic because for our iterator will hold onto the pointer as
+ // long as we don't call Next(). However, that only happens when Next() is
+ // called on the Cursor itself, at which point SQLite no longer cares
+ // about the bytes pointer.
+ sqlite::utils::ReportSqlValue(ctx, value, sqlite::utils::kSqliteStatic,
+ sqlite::utils::kSqliteStatic);
+ return SQLITE_OK;
+}
+
+int DbSqliteModule::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) {
+ return SQLITE_ERROR;
+}
+
+DbSqliteModule::QueryCost DbSqliteModule::EstimateCost(
+ const Table::Schema& schema,
+ uint32_t row_count,
+ sqlite3_index_info* info,
+ const std::vector<int>& cs_idxes,
+ const std::vector<int>& ob_idxes) {
+ // Currently our cost estimation algorithm is quite simplistic but is good
+ // enough for the simplest cases.
+ // TODO(lalitm): replace hardcoded constants with either more heuristics
+ // based on the exact type of constraint or profiling the queries
+ // themselves.
+
+ // We estimate the fixed cost of set-up and tear-down of a query in terms of
+ // the number of rows scanned.
+ constexpr double kFixedQueryCost = 1000.0;
+
+ // Setup the variables for estimating the number of rows we will have at the
+ // end of filtering. Note that |current_row_count| should always be at least
+ // 1 unless we are absolutely certain that we will return no rows as
+ // otherwise SQLite can make some bad choices.
+ uint32_t current_row_count = row_count;
+
+ // If the table is empty, any constraint set only pays the fixed cost. Also
+ // we can return 0 as the row count as we are certain that we will return no
+ // rows.
+ if (current_row_count == 0) {
+ return QueryCost{kFixedQueryCost, 0};
+ }
+
+ // Setup the variables for estimating the cost of filtering.
+ double filter_cost = 0.0;
+ for (int i : cs_idxes) {
+ if (current_row_count < 2) {
+ break;
+ }
+ const auto& c = info->aConstraint[i];
+ PERFETTO_DCHECK(c.usable);
+ PERFETTO_DCHECK(info->aConstraintUsage[i].omit);
+ PERFETTO_DCHECK(info->aConstraintUsage[i].argvIndex > 0);
+ const auto& col_schema = schema.columns[static_cast<uint32_t>(c.iColumn)];
+ if (sqlite::utils::IsOpEq(c.op) && col_schema.is_id) {
+ // If we have an id equality constraint, we can very efficiently filter
+ // down to a single row in C++. However, if we're joining with another
+ // table, SQLite will do this once per row which can be extremely
+ // expensive because of all the virtual table (which is implemented
+ // using virtual function calls) machinery. Indicate this by saying that
+ // an entire filter call is ~10x the cost of iterating a single row.
+ filter_cost += 10;
+ current_row_count = 1;
+ } else if (sqlite::utils::IsOpEq(c.op)) {
+ // If there is only a single equality constraint, we have special logic
+ // to sort by that column and then binary search if we see the
+ // constraint set often. Model this by dividing by the log of the number
+ // of rows as a good approximation. Otherwise, we'll need to do a full
+ // table scan. Alternatively, if the column is sorted, we can use the
+ // same binary search logic so we have the same low cost (even
+ // better because we don't // have to sort at all).
+ filter_cost += cs_idxes.size() == 1 || col_schema.is_sorted
+ ? log2(current_row_count)
+ : current_row_count;
+
+ // As an extremely rough heuristic, assume that an equalty constraint
+ // will cut down the number of rows by approximately double log of the
+ // number of rows.
+ double estimated_rows = current_row_count / (2 * log2(current_row_count));
+ current_row_count = std::max(static_cast<uint32_t>(estimated_rows), 1u);
+ } else if (col_schema.is_sorted &&
+ (sqlite::utils::IsOpLe(c.op) || sqlite::utils::IsOpLt(c.op) ||
+ sqlite::utils::IsOpGt(c.op) || sqlite::utils::IsOpGe(c.op))) {
+ // On a sorted column, if we see any partition constraints, we can do
+ // this filter very efficiently. Model this using the log of the number
+ // of rows as a good approximation.
+ filter_cost += log2(current_row_count);
+
+ // As an extremely rough heuristic, assume that an partition constraint
+ // will cut down the number of rows by approximately double log of the
+ // number of rows.
+ double estimated_rows = current_row_count / (2 * log2(current_row_count));
+ current_row_count = std::max(static_cast<uint32_t>(estimated_rows), 1u);
+ } else {
+ // Otherwise, we will need to do a full table scan and we estimate we
+ // will maybe (at best) halve the number of rows.
+ filter_cost += current_row_count;
+ current_row_count = std::max(current_row_count / 2u, 1u);
+ }
+ }
+
+ // Now, to figure out the cost of sorting, multiply the final row count
+ // by |qc.order_by().size()| * log(row count). This should act as a crude
+ // estimation of the cost.
+ double sort_cost =
+ static_cast<double>(static_cast<uint32_t>(ob_idxes.size()) *
+ current_row_count) *
+ log2(current_row_count);
+
+ // The cost of iterating rows is more expensive than just filtering the rows
+ // so multiply by an appropriate factor.
+ double iteration_cost = current_row_count * 2.0;
+
+ // To get the final cost, add up all the individual components.
+ double final_cost =
+ kFixedQueryCost + filter_cost + sort_cost + iteration_cost;
+ return QueryCost{final_cost, current_row_count};
+}
+
+DbSqliteModule::State::State(const Table* _table, Table::Schema _schema)
+ : State(TableComputation::kStatic, std::move(_schema)) {
+ static_table = _table;
+}
+
+DbSqliteModule::State::State(std::unique_ptr<RuntimeTable> _table)
+ : State(TableComputation::kRuntime, _table->schema()) {
+ runtime_table = std::move(_table);
+}
+
+DbSqliteModule::State::State(
+ std::unique_ptr<StaticTableFunction> _static_function)
+ : State(TableComputation::kTableFunction,
+ _static_function->CreateSchema()) {
+ static_table_function = std::move(_static_function);
+ for (const auto& c : schema.columns) {
+ argument_count += c.is_hidden;
+ }
+}
+
+DbSqliteModule::State::State(TableComputation _computation,
+ Table::Schema _schema)
+ : computation(_computation), schema(std::move(_schema)) {}
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index ded0f84..401c370 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -17,215 +17,149 @@
#ifndef SRC_TRACE_PROCESSOR_SQLITE_DB_SQLITE_TABLE_H_
#define SRC_TRACE_PROCESSOR_SQLITE_DB_SQLITE_TABLE_H_
+#include <sqlite3.h>
#include <cstdint>
-#include <functional>
-#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <vector>
-#include <sqlite3.h>
-
-#include "perfetto/base/compiler.h"
-#include "perfetto/base/status.h"
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/db/column/types.h"
#include "src/trace_processor/db/runtime_table.h"
#include "src/trace_processor/db/table.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
-#include "src/trace_processor/sqlite/query_cache.h"
-#include "src/trace_processor/sqlite/query_constraints.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
-#include "src/trace_processor/tp_metatrace.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
-struct DbSqliteTableContext {
- enum class Computation {
- // Table is statically defined.
- kStatic,
+enum class TableComputation {
+ // Table is statically defined.
+ kStatic,
- // Table is defined as a function.
- kTableFunction,
+ // Table is defined as a function.
+ kTableFunction,
- // Table is defined in runtime.
- kRuntime
- };
- DbSqliteTableContext(QueryCache* query_cache,
- const Table* table,
- Table::Schema schema);
- DbSqliteTableContext(QueryCache* query_cache,
- std::function<RuntimeTable*(std::string)> get_table,
- std::function<void(std::string)> erase_table);
- DbSqliteTableContext(QueryCache* query_cache,
- std::unique_ptr<StaticTableFunction> table);
-
- QueryCache* cache;
- Computation computation;
-
- // Only valid when computation == TableComputation::kStatic.
- const Table* static_table = nullptr;
- Table::Schema static_schema;
-
- // Only valid when computation == TableComputation::kRuntime.
- // Those functions implement the interactions with
- // PerfettoSqlEngine::runtime_tables_ to get the |runtime_table_| and erase it
- // from the map when |this| is destroyed.
- std::function<RuntimeTable*(std::string)> get_runtime_table;
- std::function<void(std::string)> erase_runtime_table;
-
- // Only valid when computation == TableComputation::kTableFunction.
- std::unique_ptr<StaticTableFunction> static_table_function;
+ // Table is defined in runtime.
+ kRuntime
};
// Implements the SQLite table interface for db tables.
-class DbSqliteTable final
- : public TypedSqliteTable<DbSqliteTable,
- std::unique_ptr<DbSqliteTableContext>> {
- public:
- using Context = DbSqliteTableContext;
- using TableComputation = Context::Computation;
+struct DbSqliteModule : public sqlite::Module<DbSqliteModule> {
+ struct State {
+ State(const Table*, Table::Schema);
+ explicit State(std::unique_ptr<RuntimeTable>);
+ explicit State(std::unique_ptr<StaticTableFunction>);
- class Cursor final : public SqliteTableLegacy::BaseCursor {
- public:
- Cursor(DbSqliteTable*, QueryCache*);
- ~Cursor() final;
+ TableComputation computation;
+ Table::Schema schema;
+ int argument_count = 0;
- Cursor(const Cursor&) = delete;
- Cursor& operator=(const Cursor&) = delete;
+ // Only valid when computation == TableComputation::kStatic.
+ const Table* static_table = nullptr;
- Cursor(Cursor&&) noexcept = delete;
- Cursor& operator=(Cursor&&) = delete;
+ // Only valid when computation == TableComputation::kRuntime.
+ std::unique_ptr<RuntimeTable> runtime_table;
- // Implementation of SqliteTableLegacy::Cursor.
- base::Status Filter(const QueryConstraints& qc,
- sqlite3_value** argv,
- FilterHistory);
-
- PERFETTO_ALWAYS_INLINE void Next() {
- if (mode_ == Mode::kSingleRow) {
- eof_ = true;
- } else {
- eof_ = !++*iterator_;
- }
- }
-
- PERFETTO_ALWAYS_INLINE bool Eof() const { return eof_; }
-
- PERFETTO_ALWAYS_INLINE void Column(sqlite3_context* ctx,
- int raw_col) const {
- auto column = static_cast<uint32_t>(raw_col);
- SqlValue value = mode_ == Mode::kSingleRow
- ? SourceTable()->columns()[column].Get(*single_row_)
- : iterator_->Get(column);
- // We can say kSqliteStatic for strings because all strings are expected
- // to come from the string pool. Thus they will be valid for the lifetime
- // of trace processor. Similarily, for bytes, we can also use
- // kSqliteStatic because for our iterator will hold onto the pointer as
- // long as we don't call Next(). However, that only happens when Next() is
- // called on the Cursor itself, at which point SQLite no longer cares
- // about the bytes pointer.
- sqlite::utils::ReportSqlValue(ctx, value, sqlite::utils::kSqliteStatic,
- sqlite::utils::kSqliteStatic);
- }
+ // Only valid when computation == TableComputation::kTableFunction.
+ std::unique_ptr<StaticTableFunction> static_table_function;
private:
+ State(TableComputation, Table::Schema);
+ };
+ struct Context {
+ std::unique_ptr<State> temporary_create_state;
+ sqlite::ModuleStateManager<DbSqliteModule> manager;
+ };
+ struct Vtab : public sqlite::Module<DbSqliteModule>::Vtab {
+ sqlite::ModuleStateManager<DbSqliteModule>::PerVtabState* state;
+ int best_index_num = 0;
+ std::string table_name;
+ };
+ struct Cursor : public sqlite::Module<DbSqliteModule>::Cursor {
enum class Mode {
kSingleRow,
kTable,
};
- // Tries to create a sorted table to cache in |sorted_cache_table_| if the
- // constraint set matches the requirements.
- void TryCacheCreateSortedTable(const QueryConstraints&, FilterHistory);
-
- const Table* SourceTable() const {
- // Try and use the sorted cache table (if it exists) to speed up the
- // sorting. Otherwise, just use the original table.
- return sorted_cache_table_ ? &*sorted_cache_table_ : upstream_table_;
- }
-
- base::Status PopulateConstraintsAndArguments(const QueryConstraints& qc,
- sqlite3_value** argv);
-
- void PopulateOrderBys(const QueryConstraints& qc);
-
- void FilterAndSortMetatrace(metatrace::Record* record);
-
- DbSqliteTable* db_sqlite_table_ = nullptr;
- QueryCache* cache_ = nullptr;
- std::vector<uint32_t> argument_index_per_column_;
-
- const Table* upstream_table_ = nullptr;
+ const Table* upstream_table = nullptr;
// Only valid for |db_sqlite_table_->computation_| ==
// TableComputation::kDynamic.
- std::unique_ptr<Table> dynamic_table_;
+ std::unique_ptr<Table> dynamic_table;
// Only valid for Mode::kSingleRow.
- std::optional<uint32_t> single_row_;
+ std::optional<uint32_t> single_row;
// Only valid for Mode::kTable.
- std::optional<Table::Iterator> iterator_;
+ std::optional<Table::Iterator> iterator;
- bool eof_ = true;
+ bool eof = true;
- // Stores a sorted version of |db_table_| sorted on a repeated equals
+ // Stores a sorted version of |db_table| sorted on a repeated equals
// constraint. This allows speeding up repeated subqueries in joins
// significantly.
- std::shared_ptr<Table> sorted_cache_table_;
+ std::optional<Table> sorted_cache_table;
// Stores the count of repeated equality queries to decide whether it is
- // wortwhile to sort |db_table_| to create |sorted_cache_table_|.
- uint32_t repeated_cache_count_ = 0;
+ // wortwhile to sort |db_table| to create |sorted_cache_table|.
+ uint32_t repeated_cache_count = 0;
- Mode mode_ = Mode::kSingleRow;
+ Mode mode = Mode::kSingleRow;
- std::vector<Constraint> constraints_;
- std::vector<Order> orders_;
- std::vector<SqlValue> table_function_arguments_;
+ int last_idx_num = -1;
+ std::vector<Constraint> constraints;
+ std::vector<Order> orders;
+ std::vector<SqlValue> table_function_arguments;
};
struct QueryCost {
double cost;
uint32_t rows;
};
- DbSqliteTable(sqlite3*, Context* context);
- virtual ~DbSqliteTable() final;
+ static constexpr bool kSupportsWrites = false;
+ static constexpr bool kDoesOverloadFunctions = false;
- // Table implementation.
- base::Status Init(int, const char* const*, SqliteTableLegacy::Schema*) final;
- std::unique_ptr<SqliteTableLegacy::BaseCursor> CreateCursor() final;
- base::Status ModifyConstraints(QueryConstraints*) final;
- int BestIndex(const QueryConstraints&, BestIndexInfo*) final;
+ static int Create(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Destroy(sqlite3_vtab*);
- // These static functions are useful to allow other callers to make use
- // of them.
- static SqliteTableLegacy::Schema ComputeSchema(const Table::Schema&,
- const char* table_name);
- static void ModifyConstraints(const Table::Schema&, QueryConstraints*);
- static void BestIndex(const Table::Schema&,
- uint32_t row_count,
- const QueryConstraints&,
- BestIndexInfo*);
+ static int Connect(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Disconnect(sqlite3_vtab*);
+
+ static int BestIndex(sqlite3_vtab*, sqlite3_index_info*);
+
+ static int Open(sqlite3_vtab*, sqlite3_vtab_cursor**);
+ static int Close(sqlite3_vtab_cursor*);
+
+ static int Filter(sqlite3_vtab_cursor*,
+ int,
+ const char*,
+ int,
+ sqlite3_value**);
+ static int Next(sqlite3_vtab_cursor*);
+ static int Eof(sqlite3_vtab_cursor*);
+ static int Column(sqlite3_vtab_cursor*, sqlite3_context*, int);
+ static int Rowid(sqlite3_vtab_cursor*, sqlite_int64*);
// static for testing.
static QueryCost EstimateCost(const Table::Schema&,
uint32_t row_count,
- const QueryConstraints& qc);
-
- private:
- Context* context_ = nullptr;
-
- // Only valid after Init has completed.
- Table::Schema schema_;
- RuntimeTable* runtime_table_;
+ sqlite3_index_info* info,
+ const std::vector<int>&,
+ const std::vector<int>&);
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_SQLITE_DB_SQLITE_TABLE_H_
diff --git a/src/trace_processor/sqlite/db_sqlite_table_unittest.cc b/src/trace_processor/sqlite/db_sqlite_table_unittest.cc
index 891ba35..29c4e65 100644
--- a/src/trace_processor/sqlite/db_sqlite_table_unittest.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table_unittest.cc
@@ -17,10 +17,15 @@
#include "src/trace_processor/sqlite/db_sqlite_table.h"
+#include <sqlite3.h>
+#include <array>
+#include <cstdint>
+
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/db/table.h"
#include "test/gtest_and_gmock.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
namespace {
Table::Schema CreateSchema() {
@@ -43,56 +48,85 @@
return schema;
}
-TEST(DbSqliteTable, IdEqCheaperThanOtherEq) {
+sqlite3_index_info::sqlite3_index_constraint CreateConstraint(int col,
+ uint8_t op) {
+ return {col, op, true, 0};
+}
+
+sqlite3_index_info::sqlite3_index_constraint_usage CreateUsage() {
+ return {1, true};
+}
+
+sqlite3_index_info CreateCsIndexInfo(
+ int cs_count,
+ sqlite3_index_info::sqlite3_index_constraint* c,
+ sqlite3_index_info::sqlite3_index_constraint_usage* u) {
+ return {cs_count, c, 0, nullptr, u, 0, nullptr, false, 0, 0, 0, 0, 0};
+}
+
+sqlite3_index_info CreateObIndexInfo(
+ int ob_count,
+ sqlite3_index_info::sqlite3_index_orderby* o) {
+ return {0, nullptr, ob_count, o, nullptr, 0, nullptr, false, 0, 0, 0, 0, 0};
+}
+
+TEST(DbSqliteModule, IdEqCheaperThanOtherEq) {
auto schema = CreateSchema();
constexpr uint32_t kRowCount = 1234;
- QueryConstraints id_eq;
- id_eq.AddConstraint(0u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
+ auto c = CreateConstraint(0, SQLITE_INDEX_CONSTRAINT_EQ);
+ auto u = CreateUsage();
+ auto info = CreateCsIndexInfo(1, &c, &u);
- auto id_cost = DbSqliteTable::EstimateCost(schema, kRowCount, id_eq);
+ auto id_cost =
+ DbSqliteModule::EstimateCost(schema, kRowCount, &info, {0u}, {});
- QueryConstraints a_eq;
- a_eq.AddConstraint(1u, SQLITE_INDEX_CONSTRAINT_EQ, 1u);
-
- auto a_cost = DbSqliteTable::EstimateCost(schema, kRowCount, a_eq);
+ c.iColumn = 1;
+ auto a_cost =
+ DbSqliteModule::EstimateCost(schema, kRowCount, &info, {0u}, {});
ASSERT_LT(id_cost.cost, a_cost.cost);
ASSERT_LT(id_cost.rows, a_cost.rows);
}
-TEST(DbSqliteTable, IdEqCheaperThatOtherConstraint) {
+TEST(DbSqliteModule, IdEqCheaperThatOtherConstraint) {
auto schema = CreateSchema();
constexpr uint32_t kRowCount = 1234;
- QueryConstraints id_eq;
- id_eq.AddConstraint(0u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
+ auto c = CreateConstraint(0, SQLITE_INDEX_CONSTRAINT_EQ);
+ auto u = CreateUsage();
+ auto info = CreateCsIndexInfo(1, &c, &u);
- auto id_cost = DbSqliteTable::EstimateCost(schema, kRowCount, id_eq);
+ auto id_cost =
+ DbSqliteModule::EstimateCost(schema, kRowCount, &info, {0u}, {});
- QueryConstraints a_eq;
- a_eq.AddConstraint(1u, SQLITE_INDEX_CONSTRAINT_LT, 1u);
-
- auto a_cost = DbSqliteTable::EstimateCost(schema, kRowCount, a_eq);
+ c.iColumn = 1;
+ c.op = SQLITE_INDEX_CONSTRAINT_LT;
+ auto a_cost =
+ DbSqliteModule::EstimateCost(schema, kRowCount, &info, {0u}, {});
ASSERT_LT(id_cost.cost, a_cost.cost);
ASSERT_LT(id_cost.rows, a_cost.rows);
}
-TEST(DbSqliteTable, SingleEqCheaperThanMultipleConstraint) {
+TEST(DbSqliteModule, SingleEqCheaperThanMultipleConstraint) {
auto schema = CreateSchema();
constexpr uint32_t kRowCount = 1234;
- QueryConstraints single_eq;
- single_eq.AddConstraint(1u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
+ auto c = CreateConstraint(1, SQLITE_INDEX_CONSTRAINT_EQ);
+ auto u = CreateUsage();
+ auto info = CreateCsIndexInfo(1, &c, &u);
- auto single_cost = DbSqliteTable::EstimateCost(schema, kRowCount, single_eq);
+ auto single_cost =
+ DbSqliteModule::EstimateCost(schema, kRowCount, &info, {0u}, {});
- QueryConstraints multi_eq;
- multi_eq.AddConstraint(1u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
- multi_eq.AddConstraint(2u, SQLITE_INDEX_CONSTRAINT_EQ, 1u);
+ std::array c2{CreateConstraint(1, SQLITE_INDEX_CONSTRAINT_EQ),
+ CreateConstraint(2, SQLITE_INDEX_CONSTRAINT_EQ)};
+ std::array u2{CreateUsage(), CreateUsage()};
+ auto info2 = CreateCsIndexInfo(c2.size(), c2.data(), u2.data());
- auto multi_cost = DbSqliteTable::EstimateCost(schema, kRowCount, multi_eq);
+ auto multi_cost =
+ DbSqliteModule::EstimateCost(schema, kRowCount, &info2, {0u, 1u}, {});
// The cost of the single filter should be cheaper (because of our special
// handling of single equality). But the number of rows should be greater.
@@ -100,22 +134,25 @@
ASSERT_GT(single_cost.rows, multi_cost.rows);
}
-TEST(DbSqliteTable, MultiSortedEqCheaperThanMultiUnsortedEq) {
+TEST(DbSqliteModule, MultiSortedEqCheaperThanMultiUnsortedEq) {
auto schema = CreateSchema();
constexpr uint32_t kRowCount = 1234;
- QueryConstraints sorted_eq;
- sorted_eq.AddConstraint(2u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
- sorted_eq.AddConstraint(3u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
+ std::array c1{CreateConstraint(1, SQLITE_INDEX_CONSTRAINT_EQ),
+ CreateConstraint(2, SQLITE_INDEX_CONSTRAINT_EQ)};
+ std::array u1{CreateUsage(), CreateUsage()};
+ auto info1 = CreateCsIndexInfo(c1.size(), c1.data(), u1.data());
- auto sorted_cost = DbSqliteTable::EstimateCost(schema, kRowCount, sorted_eq);
+ auto sorted_cost =
+ DbSqliteModule::EstimateCost(schema, kRowCount, &info1, {0u, 1u}, {});
- QueryConstraints unsorted_eq;
- unsorted_eq.AddConstraint(3u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
- unsorted_eq.AddConstraint(4u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
+ std::array c2{CreateConstraint(3, SQLITE_INDEX_CONSTRAINT_EQ),
+ CreateConstraint(4, SQLITE_INDEX_CONSTRAINT_EQ)};
+ std::array u2{CreateUsage(), CreateUsage()};
+ auto info2 = CreateCsIndexInfo(c2.size(), c2.data(), u2.data());
auto unsorted_cost =
- DbSqliteTable::EstimateCost(schema, kRowCount, unsorted_eq);
+ DbSqliteModule::EstimateCost(schema, kRowCount, &info2, {0u, 1u}, {});
// The number of rows should be the same but the cost of the sorted
// query should be less.
@@ -123,41 +160,49 @@
ASSERT_EQ(sorted_cost.rows, unsorted_cost.rows);
}
-TEST(DbSqliteTable, EmptyTableCosting) {
+TEST(DbSqliteModule, EmptyTableCosting) {
auto schema = CreateSchema();
+ constexpr uint32_t kRowCount = 0;
- QueryConstraints id_eq;
- id_eq.AddConstraint(0u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
+ std::array c1{CreateConstraint(0, SQLITE_INDEX_CONSTRAINT_EQ)};
+ std::array u1{CreateUsage()};
+ auto info1 = CreateCsIndexInfo(c1.size(), c1.data(), u1.data());
- auto id_cost = DbSqliteTable::EstimateCost(schema, 0, id_eq);
+ auto id_cost =
+ DbSqliteModule::EstimateCost(schema, kRowCount, &info1, {0u}, {});
- QueryConstraints a_eq;
- a_eq.AddConstraint(1u, SQLITE_INDEX_CONSTRAINT_LT, 1u);
+ std::array c2{CreateConstraint(0, SQLITE_INDEX_CONSTRAINT_EQ)};
+ std::array u2{CreateUsage()};
+ auto info2 = CreateCsIndexInfo(c2.size(), c2.data(), u2.data());
- auto a_cost = DbSqliteTable::EstimateCost(schema, 0, a_eq);
+ auto a_cost =
+ DbSqliteModule::EstimateCost(schema, kRowCount, &info2, {0u}, {});
ASSERT_DOUBLE_EQ(id_cost.cost, a_cost.cost);
ASSERT_EQ(id_cost.rows, a_cost.rows);
}
-TEST(DbSqliteTable, OrderByOnSortedCheaper) {
+TEST(DbSqliteModule, OrderByOnSortedCheaper) {
auto schema = CreateSchema();
constexpr uint32_t kRowCount = 1234;
- QueryConstraints a_qc;
- a_qc.AddOrderBy(1u, false);
+ sqlite3_index_info::sqlite3_index_orderby ob1{1u, false};
+ auto info1 = CreateObIndexInfo(1, &ob1);
- auto a_cost = DbSqliteTable::EstimateCost(schema, kRowCount, a_qc);
+ auto a_cost =
+ DbSqliteModule::EstimateCost(schema, kRowCount, &info1, {}, {0u});
+
+ sqlite3_index_info::sqlite3_index_orderby ob2{2u, false};
+ auto info2 = CreateObIndexInfo(1, &ob2);
// On an ordered column, the constraint for sorting would get pruned so
// we would end up with an empty constraint set.
- QueryConstraints sorted_qc;
- auto sorted_cost = DbSqliteTable::EstimateCost(schema, kRowCount, sorted_qc);
+ auto sorted_cost =
+ DbSqliteModule::EstimateCost(schema, kRowCount, &info2, {}, {});
ASSERT_LT(sorted_cost.cost, a_cost.cost);
ASSERT_EQ(sorted_cost.rows, a_cost.rows);
}
} // namespace
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/sqlite/module_lifecycle_manager.h b/src/trace_processor/sqlite/module_lifecycle_manager.h
index 0e44a6d..66054d5 100644
--- a/src/trace_processor/sqlite/module_lifecycle_manager.h
+++ b/src/trace_processor/sqlite/module_lifecycle_manager.h
@@ -19,6 +19,7 @@
#include <memory>
#include <string>
+#include <string_view>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/flat_hash_map.h"
@@ -117,6 +118,16 @@
return s->state.get();
}
+ // Looks up the state of a module by name. This function should only be called
+ // for speculative lookups from outside the module implementation: use
+ // |GetState| inside the sqlite::Module implementation.
+ typename Module::State* FindStateByName(std::string_view name) {
+ if (auto ptr = state_by_name_.Find(std::string(name)); ptr) {
+ return GetState(ptr->get());
+ }
+ return nullptr;
+ }
+
private:
base::FlatHashMap<std::string, std::unique_ptr<PerVtabState>> state_by_name_;
};
diff --git a/src/trace_processor/sqlite/query_cache.h b/src/trace_processor/sqlite/query_cache.h
deleted file mode 100644
index 30f6045..0000000
--- a/src/trace_processor/sqlite/query_cache.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed 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.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_SQLITE_QUERY_CACHE_H_
-#define SRC_TRACE_PROCESSOR_SQLITE_QUERY_CACHE_H_
-
-#include <algorithm>
-#include <functional>
-#include <memory>
-#include <vector>
-
-#include "src/trace_processor/db/table.h"
-#include "src/trace_processor/sqlite/query_constraints.h"
-
-namespace perfetto::trace_processor {
-
-// Implements a simple caching strategy for commonly executed queries.
-// TODO(lalitm): the design of this class is very experimental. It was mainly
-// introduced to solve a specific problem (slow process summary tracks in the
-// Perfetto UI) and should not be modified without a full design discussion.
-class QueryCache {
- public:
- using Constraint = QueryConstraints::Constraint;
-
- // Returns a cached table if the passed query set are currenly cached or
- // nullptr otherwise.
- std::shared_ptr<Table> GetIfCached(const Table* source,
- const std::vector<Constraint>& cs) const {
- if (cached_.source != source || cs.size() != cached_.constraints.size())
- return nullptr;
-
- auto p = [](const Constraint& a, const Constraint& b) {
- return a.column == b.column && a.op == b.op;
- };
- bool same_cs =
- std::equal(cs.begin(), cs.end(), cached_.constraints.begin(), p);
- return same_cs ? cached_.table : nullptr;
- }
-
- // Caches the table with the given source, constraint and order set. Returns
- // a pointer to the newly cached table.
- std::shared_ptr<Table> GetOrCache(
- const Table* source,
- const std::vector<QueryConstraints::Constraint>& cs,
- std::function<Table()> fn) {
- std::shared_ptr<Table> cached = GetIfCached(source, cs);
- if (cached)
- return cached;
-
- cached_.source = source;
- cached_.constraints = cs;
- cached_.table.reset(new Table(fn()));
- return cached_.table;
- }
-
- private:
- struct CachedTable {
- std::shared_ptr<Table> table;
-
- const Table* source = nullptr;
- std::vector<Constraint> constraints;
- };
-
- CachedTable cached_;
-};
-
-} // namespace perfetto::trace_processor
-
-#endif // SRC_TRACE_PROCESSOR_SQLITE_QUERY_CACHE_H_
diff --git a/src/trace_processor/sqlite/query_constraints.cc b/src/trace_processor/sqlite/query_constraints.cc
deleted file mode 100644
index 67f1812..0000000
--- a/src/trace_processor/sqlite/query_constraints.cc
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed 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.
- */
-
-#include "src/trace_processor/sqlite/query_constraints.h"
-
-#include <sqlite3.h>
-
-#include <string>
-
-#include "perfetto/ext/base/string_splitter.h"
-#include "perfetto/ext/base/string_utils.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-QueryConstraints::QueryConstraints(uint64_t cols_used)
- : cols_used_(cols_used) {}
-QueryConstraints::~QueryConstraints() = default;
-QueryConstraints::QueryConstraints(QueryConstraints&&) noexcept = default;
-QueryConstraints& QueryConstraints::operator=(QueryConstraints&&) noexcept =
- default;
-
-int QueryConstraints::FreeSqliteString(char* resource) {
- sqlite3_free(resource);
- return 0;
-}
-
-bool QueryConstraints::operator==(const QueryConstraints& other) const {
- if ((other.constraints().size() != constraints().size()) ||
- (other.order_by().size() != order_by().size()) ||
- other.cols_used() != cols_used()) {
- return false;
- }
-
- for (size_t i = 0; i < constraints().size(); ++i) {
- if ((constraints()[i].column != other.constraints()[i].column) ||
- (constraints()[i].op != other.constraints()[i].op)) {
- return false;
- }
- }
-
- for (size_t i = 0; i < order_by().size(); ++i) {
- if ((order_by()[i].iColumn != other.order_by()[i].iColumn) ||
- (order_by()[i].desc != other.order_by()[i].desc)) {
- return false;
- }
- }
-
- return true;
-}
-
-void QueryConstraints::AddConstraint(int column,
- unsigned char op,
- int aconstraint_idx) {
- Constraint c{};
- c.column = column;
- c.op = op;
- c.a_constraint_idx = aconstraint_idx;
- constraints_.emplace_back(c);
-}
-
-void QueryConstraints::AddOrderBy(int column, unsigned char desc) {
- OrderBy ob{};
- ob.iColumn = column;
- ob.desc = desc;
- order_by_.emplace_back(ob);
-}
-
-QueryConstraints::SqliteString QueryConstraints::ToNewSqlite3String() const {
- std::string str_result;
- str_result.reserve(512);
-
- // Add all the constraints.
- str_result.append("C");
- str_result.append(std::to_string(constraints_.size()));
- str_result.append(",");
- for (const auto& cs : constraints_) {
- str_result.append(std::to_string(cs.column));
- str_result.append(",");
- str_result.append(std::to_string(cs.op));
- str_result.append(",");
- }
- str_result.back() = ';';
-
- // Add all the clauses.
- str_result.append("O");
- str_result.append(std::to_string(order_by_.size()));
- str_result.append(",");
- for (const auto& ob : order_by_) {
- str_result.append(std::to_string(ob.iColumn));
- str_result.append(",");
- str_result.append(std::to_string(ob.desc));
- str_result.append(",");
- }
- str_result.back() = ';';
-
- // Add the columns used.
- str_result.append("U");
- str_result.append(std::to_string(cols_used_));
-
- SqliteString result(static_cast<char*>(
- sqlite3_malloc(static_cast<int>(str_result.size() + 1))));
- base::StringCopy(result.get(), str_result.c_str(), str_result.size() + 1);
- return result;
-}
-
-QueryConstraints QueryConstraints::FromString(const char* idxStr) {
- QueryConstraints qc;
-
- base::StringSplitter outer_splitter(std::string(idxStr), ';');
-
- // Handle the CONSTRAINT section of the string.
- PERFETTO_CHECK(outer_splitter.Next() && outer_splitter.cur_token_size() > 1);
- {
- base::StringSplitter splitter(&outer_splitter, ',');
- PERFETTO_CHECK(splitter.Next() && splitter.cur_token_size() > 1);
-
- // The '[1]' skips the letter 'C' in the first token.
- int64_t num_constraints = *base::CStringToInt64(&splitter.cur_token()[1]);
- for (int i = 0; i < num_constraints; ++i) {
- PERFETTO_CHECK(splitter.Next());
- int col = static_cast<int>(*base::CStringToInt32(splitter.cur_token()));
- PERFETTO_CHECK(splitter.Next());
- unsigned char op = static_cast<unsigned char>(
- *base::CStringToUInt32(splitter.cur_token()));
- qc.AddConstraint(col, op, 0);
- }
- }
-
- // Handle the ORDER BY section of the string.
- PERFETTO_CHECK(outer_splitter.Next() && outer_splitter.cur_token_size() > 1);
- {
- base::StringSplitter splitter(&outer_splitter, ',');
- PERFETTO_CHECK(splitter.Next() && splitter.cur_token_size() > 1);
-
- // The '[1]' skips the letter 'O' in the current token.
- int64_t num_order_by = *base::CStringToInt64(&splitter.cur_token()[1]);
- for (int i = 0; i < num_order_by; ++i) {
- PERFETTO_CHECK(splitter.Next());
- int col = static_cast<int>(*base::CStringToInt32(splitter.cur_token()));
- PERFETTO_CHECK(splitter.Next());
- unsigned char desc = static_cast<unsigned char>(
- *base::CStringToUInt32(splitter.cur_token()));
- qc.AddOrderBy(col, desc);
- }
- }
-
- // Handle the COLS USED section of the string.
- PERFETTO_CHECK(outer_splitter.Next() && outer_splitter.cur_token_size() > 1);
- {
- // The '[1]' skips the letter 'U' in the current token.
- qc.cols_used_ = *base::CStringToUInt64(&outer_splitter.cur_token()[1]);
- }
-
- PERFETTO_DCHECK(!outer_splitter.Next());
- return qc;
-}
-
-} // namespace trace_processor
-} // namespace perfetto
diff --git a/src/trace_processor/sqlite/query_constraints.h b/src/trace_processor/sqlite/query_constraints.h
deleted file mode 100644
index 8fc08e7..0000000
--- a/src/trace_processor/sqlite/query_constraints.h
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed 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.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_SQLITE_QUERY_CONSTRAINTS_H_
-#define SRC_TRACE_PROCESSOR_SQLITE_QUERY_CONSTRAINTS_H_
-
-#include <cstdint>
-#include <limits>
-#include <vector>
-
-#include "perfetto/ext/base/scoped_file.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-// This class stores the constraints (including the order-by information) for
-// a query on a sqlite3 virtual table and handles their de/serialization into
-// strings.
-// This is because the constraint columns and the order-by clauses are passed
-// to the xBestIndex method but the constraint values are available only in the
-// xFilter method. Unfortunately sqlite vtable API don't give any hint about
-// the validity of the constraints (i.e. constraints passed to xBestIndex can
-// be used by future xFilter calls in the far future). The only mechanism
-// offered by sqlite is the idxStr string which is returned by the vtable
-// in the xBestIndex call and passed to each corresponding xFilter call.
-class QueryConstraints {
- public:
- struct Constraint {
- // Column this constraint refers to.
- int column;
-
- // SQLite op for the constraint.
- int op;
-
- // The original index of this constraint in the aConstraint array.
- // Used internally by SqliteTableLegacy for xBestIndex - this should not be
- // read or modified by subclasses of SqliteTableLegacy.
- int a_constraint_idx;
- };
- struct OrderBy {
- int iColumn;
- unsigned char desc;
- };
-
- static int FreeSqliteString(char* resource);
-
- using SqliteString = base::ScopedResource<char*, FreeSqliteString, nullptr>;
-
- explicit QueryConstraints(
- uint64_t cols_used = std::numeric_limits<uint64_t>::max());
- ~QueryConstraints();
- QueryConstraints(QueryConstraints&&) noexcept;
- QueryConstraints& operator=(QueryConstraints&&) noexcept;
-
- // Two QueryConstraints with the same constraint and orderby vectors
- // are equal.
- bool operator==(const QueryConstraints& other) const;
-
- void AddConstraint(int column, unsigned char op, int aconstraint_idx);
-
- void AddOrderBy(int column, unsigned char desc);
-
- void ClearOrderBy() { order_by_.clear(); }
-
- // Converts the constraints and order by information to a string for
- // use by sqlite.
- SqliteString ToNewSqlite3String() const;
-
- // Deserializes the string into QueryConstraints. String given is in the form
- // C{# of constraints},col1,op1,col2,op2...,O{# of order by},col1,desc1...
- // For example C1,0,3,O2,1,0,4,1
- static QueryConstraints FromString(const char* idxStr);
-
- const std::vector<OrderBy>& order_by() const { return order_by_; }
-
- const std::vector<Constraint>& constraints() const { return constraints_; }
-
- std::vector<OrderBy>* mutable_order_by() { return &order_by_; }
-
- std::vector<Constraint>* mutable_constraints() { return &constraints_; }
-
- uint64_t cols_used() const { return cols_used_; }
-
- private:
- QueryConstraints(const QueryConstraints&) = delete;
- QueryConstraints& operator=(const QueryConstraints&) = delete;
-
- std::vector<OrderBy> order_by_;
- std::vector<Constraint> constraints_;
-
- // Stores information about which column is used by this query.
- // If the lowest bit of is set, the first column is used. The second lowest
- // bit corresponds to the second column etc. If the most significant bit is
- // set, that means that any column after the first 63 columns could be used.
- uint64_t cols_used_ = std::numeric_limits<uint64_t>::max();
-};
-
-} // namespace trace_processor
-} // namespace perfetto
-
-#endif // SRC_TRACE_PROCESSOR_SQLITE_QUERY_CONSTRAINTS_H_
diff --git a/src/trace_processor/sqlite/query_constraints_unittest.cc b/src/trace_processor/sqlite/query_constraints_unittest.cc
deleted file mode 100644
index 830931e..0000000
--- a/src/trace_processor/sqlite/query_constraints_unittest.cc
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed 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.
- */
-
-#include "src/trace_processor/sqlite/query_constraints.h"
-
-#include <sqlite3.h>
-
-#include "perfetto/base/logging.h"
-#include "test/gtest_and_gmock.h"
-
-using testing::ElementsAreArray;
-using testing::Field;
-using testing::Matcher;
-using testing::Matches;
-using testing::Pointwise;
-
-namespace perfetto {
-namespace trace_processor {
-namespace {
-
-class QueryConstraintsTest : public ::testing::Test {
- public:
- QueryConstraintsTest() { PERFETTO_CHECK(sqlite3_initialize() == SQLITE_OK); }
-};
-
-TEST_F(QueryConstraintsTest, ConvertToAndFromSqlString) {
- QueryConstraints qc(0);
- qc.AddConstraint(12, 0, 0);
-
- QueryConstraints::SqliteString only_constraint = qc.ToNewSqlite3String();
- ASSERT_TRUE(strcmp(only_constraint.get(), "C1,12,0;O0;U0") == 0);
-
- QueryConstraints qc_constraint =
- QueryConstraints::FromString(only_constraint.get());
- ASSERT_EQ(qc, qc_constraint);
-
- qc.AddOrderBy(1, false);
- qc.AddOrderBy(21, true);
-
- QueryConstraints::SqliteString result = qc.ToNewSqlite3String();
- ASSERT_TRUE(strcmp(result.get(), "C1,12,0;O2,1,0,21,1;U0") == 0);
-
- QueryConstraints qc_result = QueryConstraints::FromString(result.get());
- ASSERT_EQ(qc, qc_result);
-}
-
-TEST_F(QueryConstraintsTest, CheckEmptyConstraints) {
- QueryConstraints qc(0);
-
- QueryConstraints::SqliteString string_result = qc.ToNewSqlite3String();
- ASSERT_TRUE(strcmp(string_result.get(), "C0;O0;U0") == 0);
-
- QueryConstraints qc_result =
- QueryConstraints::FromString(string_result.get());
- ASSERT_EQ(qc_result.constraints().size(), 0u);
- ASSERT_EQ(qc_result.order_by().size(), 0u);
-}
-
-TEST_F(QueryConstraintsTest, OnlyOrderBy) {
- QueryConstraints qc(0);
- qc.AddOrderBy(3, true);
-
- QueryConstraints::SqliteString string_result = qc.ToNewSqlite3String();
- ASSERT_TRUE(strcmp(string_result.get(), "C0;O1,3,1;U0") == 0);
-
- QueryConstraints qc_result =
- QueryConstraints::FromString(string_result.get());
- ASSERT_EQ(qc, qc_result);
-}
-
-TEST_F(QueryConstraintsTest, ColsUsed) {
- ASSERT_EQ(QueryConstraints(0), QueryConstraints::FromString("C0;O0;U0"));
-
- ASSERT_EQ(QueryConstraints(4), QueryConstraints::FromString("C0;O0;U4"));
-
- ASSERT_EQ(QueryConstraints(1ull << 63),
- QueryConstraints::FromString("C0;O0;U9223372036854775808"));
-
- ASSERT_EQ(QueryConstraints(9223372036854775807ull),
- QueryConstraints::FromString("C0;O0;U9223372036854775807"));
-
- ASSERT_EQ(QueryConstraints(),
- QueryConstraints::FromString("C0;O0;U18446744073709551615"));
-
- auto str = QueryConstraints(0xFFFFFFFFFFFFFFFF).ToNewSqlite3String();
- ASSERT_STREQ(str.get(), "C0;O0;U18446744073709551615");
-}
-
-} // namespace
-} // namespace trace_processor
-} // namespace perfetto
diff --git a/src/trace_processor/sqlite/sqlite_engine.cc b/src/trace_processor/sqlite/sqlite_engine.cc
index 00defc2..cfd73dc 100644
--- a/src/trace_processor/sqlite/sqlite_engine.cc
+++ b/src/trace_processor/sqlite/sqlite_engine.cc
@@ -16,21 +16,20 @@
#include "src/trace_processor/sqlite/sqlite_engine.h"
-#include <memory>
+#include <cstdint>
#include <optional>
-#include <unordered_set>
+#include <string>
#include <utility>
-#include <vector>
+#include "perfetto/base/build_config.h"
+#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
-#include "perfetto/ext/base/string_utils.h"
#include "perfetto/public/compiler.h"
-#include "src/trace_processor/sqlite/db_sqlite_table.h"
-#include "src/trace_processor/sqlite/query_cache.h"
#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sql_source.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/tp_metatrace.h"
+
+#include "protos/perfetto/trace_processor/metatrace_categories.pbzero.h"
// In Android and Chromium tree builds, we don't have the percentile module.
// Just don't include it.
@@ -41,14 +40,13 @@
const sqlite3_api_routines* api);
#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_PERCENTILE)
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
namespace {
void EnsureSqliteInitialized() {
- // sqlite3_initialize isn't actually thread-safe despite being documented
- // as such; we need to make sure multiple TraceProcessorImpl instances don't
- // call it concurrently and only gets called once per process, instead.
+ // sqlite3_initialize isn't actually thread-safe in standalone builds because
+ // we build with SQLITE_THREADSAFE=0. Ensure it's only called from a single
+ // thread.
static bool init_once = [] { return sqlite3_initialize() == SQLITE_OK; }();
PERFETTO_CHECK(init_once);
}
@@ -60,7 +58,6 @@
PERFETTO_FATAL("Error setting pragma temp_store: %s", error);
}
// In Android tree builds, we don't have the percentile module.
-// Just don't include it.
#if PERFETTO_BUILDFLAG(PERFETTO_TP_PERCENTILE)
sqlite3_percentile_init(db, &error, nullptr);
if (error) {
@@ -91,15 +88,10 @@
PERFETTO_CHECK(sqlite3_open_v2(":memory:", &db, kSqliteOpenFlags, nullptr) ==
SQLITE_OK);
InitializeSqlite(db);
- db_.reset(std::move(db));
+ db_.reset(db);
}
SqliteEngine::~SqliteEngine() {
- // IMPORTANT: the order of operations in this destructor is very sensitive and
- // should not be changed without careful consideration of the consequences.
- // Thankfully, because we are very aggressive with PERFETTO_CHECK, mistakes
- // will usually manifest as crashes, but this is not guaranteed.
-
// It is important to unregister any functions that have been registered with
// the database before destroying it. This is because functions can hold onto
// prepared statements, which must be finalized before database destruction.
@@ -112,52 +104,6 @@
}
}
fn_ctx_.Clear();
-
- // Drop any explicitly created virtual tables before destroying the database
- // so that any prepared statements are correctly finalized. Note that we need
- // to do this in two steps (first create all the SQLs before then executing
- // them) because |OnSqliteTableDestroyed| will be called as each DROP is
- // executed.
- std::vector<std::string> drop_stmts;
- std::unordered_set<std::string> dropped_tables;
- for (auto it = all_created_sqlite_tables_.rbegin();
- it != all_created_sqlite_tables_.rend(); it++) {
- if (auto* type = sqlite_tables_.Find(*it);
- !type || *type != SqliteTableLegacy::TableType::kExplicitCreate) {
- continue;
- }
- if (auto it_and_ins = dropped_tables.insert(*it); !it_and_ins.second) {
- continue;
- }
- base::StackString<1024> drop("DROP TABLE %s", it->c_str());
- drop_stmts.emplace_back(drop.ToStdString());
- }
- for (const auto& drop : drop_stmts) {
- int ret = sqlite3_exec(db(), drop.c_str(), nullptr, nullptr, nullptr);
- if (PERFETTO_UNLIKELY(ret != SQLITE_OK)) {
- PERFETTO_FATAL("Failed to execute statement: '%s'", drop.c_str());
- }
- }
-
- // SQLite will not pick saved tables back up when destroyed as, from it's
- // perspective, it has called xDisconnect. Make sure to do that ourselves.
- saved_tables_.Clear();
-
- // Reset the database itself. We need to do this after clearing the saved
- // tables as the saved tables could hold onto prepared statements.
- db_.reset();
-
- // The above operations should have cleared all the tables.
- if (PERFETTO_UNLIKELY(sqlite_tables_.size() != 0)) {
- std::vector<std::string> tables;
- for (auto it = sqlite_tables_.GetIterator(); it; ++it) {
- tables.push_back(it.key());
- }
- std::string joined = base::Join(tables, ",");
- PERFETTO_FATAL(
- "SqliteTable instances still exist: count='%zu', tables='[%s]'",
- sqlite_tables_.size(), joined.c_str());
- }
}
SqliteEngine::PreparedStatement SqliteEngine::PrepareStatement(SqlSource sql) {
@@ -256,30 +202,6 @@
return base::OkStatus();
}
-base::Status SqliteEngine::SaveSqliteTable(
- const std::string& table_name,
- std::unique_ptr<SqliteTableLegacy> table) {
- auto res = saved_tables_.Insert(table_name, {});
- if (!res.second) {
- return base::ErrStatus("Table with name %s already is saved",
- table_name.c_str());
- }
- *res.first = std::move(table);
- return base::OkStatus();
-}
-
-base::StatusOr<std::unique_ptr<SqliteTableLegacy>>
-SqliteEngine::RestoreSqliteTable(const std::string& table_name) {
- auto* res = saved_tables_.Find(table_name);
- if (!res) {
- return base::ErrStatus("Table with name %s does not exist in saved state",
- table_name.c_str());
- }
- std::unique_ptr<SqliteTableLegacy> table = std::move(*res);
- PERFETTO_CHECK(saved_tables_.Erase(table_name));
- return std::move(table);
-}
-
void* SqliteEngine::GetFunctionContext(const std::string& name, int argc) {
auto* res = fn_ctx_.Find(std::make_pair(name, argc));
return res ? *res : nullptr;
@@ -289,17 +211,6 @@
return GetErrorOffsetDb(db_.get());
}
-void SqliteEngine::OnSqliteTableCreated(const std::string& name,
- SqliteTableLegacy::TableType type) {
- auto it_and_inserted = sqlite_tables_.Insert(name, type);
- PERFETTO_CHECK(it_and_inserted.second);
- all_created_sqlite_tables_.push_back(name);
-}
-
-void SqliteEngine::OnSqliteTableDestroyed(const std::string& name) {
- PERFETTO_CHECK(sqlite_tables_.Erase(name));
-}
-
SqliteEngine::PreparedStatement::PreparedStatement(ScopedStmt stmt,
SqlSource source)
: stmt_(std::move(stmt)),
@@ -342,5 +253,4 @@
return expanded_sql_.get();
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/sqlite/sqlite_engine.h b/src/trace_processor/sqlite/sqlite_engine.h
index 55e991f..6d6ad13 100644
--- a/src/trace_processor/sqlite/sqlite_engine.h
+++ b/src/trace_processor/sqlite/sqlite_engine.h
@@ -18,29 +18,23 @@
#define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_ENGINE_H_
#include <sqlite3.h>
-#include <stdint.h>
-#include <functional>
+#include <cstddef>
+#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
-#include <vector>
+#include <utility>
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/hash.h"
-#include "src/trace_processor/db/table.h"
#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
-#include "src/trace_processor/sqlite/query_cache.h"
#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sql_source.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
-#include "src/trace_processor/tp_metatrace.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
// Wrapper class around SQLite C API.
//
@@ -93,6 +87,9 @@
SqliteEngine();
~SqliteEngine();
+ SqliteEngine(SqliteEngine&&) noexcept = delete;
+ SqliteEngine& operator=(SqliteEngine&&) = delete;
+
// Prepares a SQLite statement for the given SQL.
PreparedStatement PrepareStatement(SqlSource);
@@ -137,34 +134,12 @@
void RegisterVirtualTableModule(const std::string& module_name,
std::unique_ptr<typename Module::Context>);
- // Registers a SQLite virtual table module with the given name.
- template <typename Vtab, typename Context>
- void RegisterVirtualTableModule(const std::string& module_name,
- Context ctx,
- SqliteTableLegacy::TableType table_type,
- bool updatable);
-
// Declares a virtual table with SQLite.
base::Status DeclareVirtualTable(const std::string& create_stmt);
- // Saves a SQLite table across a pair of xDisconnect/xConnect callbacks.
- base::Status SaveSqliteTable(const std::string& table_name,
- std::unique_ptr<SqliteTableLegacy>);
-
- // Restores a SQLite table across a pair of xDisconnect/xConnect callbacks.
- base::StatusOr<std::unique_ptr<SqliteTableLegacy>> RestoreSqliteTable(
- const std::string& table_name);
-
// Gets the context for a registered SQL function.
void* GetFunctionContext(const std::string& name, int argc);
- // Should be called when a SqliteTableLegacy instance is created.
- void OnSqliteTableCreated(const std::string& name,
- SqliteTableLegacy::TableType);
-
- // Should be called when a SqliteTableLegacy instance is destroyed.
- void OnSqliteTableDestroyed(const std::string& name);
-
sqlite3* db() const { return db_.get(); }
private:
@@ -179,20 +154,11 @@
std::optional<uint32_t> GetErrorOffset() const;
- SqliteEngine(SqliteEngine&&) noexcept = delete;
- SqliteEngine& operator=(SqliteEngine&&) = delete;
-
- base::FlatHashMap<std::string, SqliteTableLegacy::TableType> sqlite_tables_;
- std::vector<std::string> all_created_sqlite_tables_;
- base::FlatHashMap<std::string, std::unique_ptr<SqliteTableLegacy>>
- saved_tables_;
base::FlatHashMap<std::pair<std::string, int>, void*, FnHasher> fn_ctx_;
-
ScopedDb db_;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
// The rest of this file is just implementation details which we need
// in the header file because it is templated code. We separate it out
@@ -222,24 +188,6 @@
PERFETTO_CHECK(res == SQLITE_OK);
}
-template <typename Vtab, typename Context>
-void SqliteEngine::RegisterVirtualTableModule(
- const std::string& module_name,
- Context ctx,
- SqliteTableLegacy::TableType table_type,
- bool updatable) {
- static_assert(std::is_base_of_v<SqliteTableLegacy, Vtab>,
- "Must subclass TypedSqliteTable");
-
- auto module_arg =
- Vtab::CreateModuleArg(this, std::move(ctx), table_type, updatable);
- sqlite3_module* module = &module_arg->module;
- int res = sqlite3_create_module_v2(
- db_.get(), module_name.c_str(), module, module_arg.release(),
- [](void* arg) { delete static_cast<typename Vtab::ModuleArg*>(arg); });
- PERFETTO_CHECK(res == SQLITE_OK);
-}
-
} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_ENGINE_H_
diff --git a/src/trace_processor/sqlite/sqlite_table.cc b/src/trace_processor/sqlite/sqlite_table.cc
deleted file mode 100644
index a56fa6e..0000000
--- a/src/trace_processor/sqlite/sqlite_table.cc
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed 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.
- */
-
-#include "src/trace_processor/sqlite/sqlite_table.h"
-
-#include <string.h>
-#include <algorithm>
-#include <cinttypes>
-#include <map>
-#include <memory>
-
-#include "perfetto/base/logging.h"
-#include "perfetto/base/status.h"
-#include "perfetto/ext/base/status_or.h"
-#include "perfetto/ext/base/string_view.h"
-#include "sqlite3.h"
-#include "src/trace_processor/sqlite/sqlite_engine.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
-#include "src/trace_processor/tp_metatrace.h"
-#include "src/trace_processor/util/status_macros.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-namespace {
-
-std::string OpToDebugString(int op) {
- switch (op) {
- case SQLITE_INDEX_CONSTRAINT_EQ:
- return "=";
- case SQLITE_INDEX_CONSTRAINT_NE:
- return "!=";
- case SQLITE_INDEX_CONSTRAINT_GE:
- return ">=";
- case SQLITE_INDEX_CONSTRAINT_GT:
- return ">";
- case SQLITE_INDEX_CONSTRAINT_LE:
- return "<=";
- case SQLITE_INDEX_CONSTRAINT_LT:
- return "<";
- case SQLITE_INDEX_CONSTRAINT_LIKE:
- return "like";
- case SQLITE_INDEX_CONSTRAINT_ISNULL:
- return "is null";
- case SQLITE_INDEX_CONSTRAINT_ISNOTNULL:
- return "is not null";
- case SQLITE_INDEX_CONSTRAINT_IS:
- return "is";
- case SQLITE_INDEX_CONSTRAINT_ISNOT:
- return "is not";
- case SQLITE_INDEX_CONSTRAINT_GLOB:
- return "glob";
- case SQLITE_INDEX_CONSTRAINT_LIMIT:
- return "limit";
- case SQLITE_INDEX_CONSTRAINT_OFFSET:
- return "offset";
- case SqliteTableLegacy::CustomFilterOpcode::kSourceGeqOpCode:
- return "source_geq";
- default:
- PERFETTO_FATAL("Operator to string conversion not impemented for %d", op);
- }
-}
-
-void ConstraintsToString(const QueryConstraints& qc,
- const SqliteTableLegacy::Schema& schema,
- std::string& out) {
- bool is_first = true;
- for (const auto& cs : qc.constraints()) {
- if (!is_first) {
- out.append(",");
- }
- out.append(schema.columns()[static_cast<size_t>(cs.column)].name());
- out.append(" ");
- out.append(OpToDebugString(cs.op));
- is_first = false;
- }
-}
-
-void OrderByToString(const QueryConstraints& qc,
- const SqliteTableLegacy::Schema& schema,
- std::string& out) {
- bool is_first = true;
- for (const auto& ob : qc.order_by()) {
- if (!is_first) {
- out.append(",");
- }
- out.append(schema.columns()[static_cast<size_t>(ob.iColumn)].name());
- out.append(" ");
- out.append(std::to_string(ob.desc));
- is_first = false;
- }
-}
-
-std::string QcDebugStr(const QueryConstraints& qc,
- const SqliteTableLegacy::Schema& schema) {
- std::string str_result;
- str_result.reserve(512);
-
- str_result.append("C");
- str_result.append(std::to_string(qc.constraints().size()));
- str_result.append(",");
- ConstraintsToString(qc, schema, str_result);
- str_result.append(";");
-
- str_result.append("O");
- str_result.append(std::to_string(qc.order_by().size()));
- str_result.append(",");
- OrderByToString(qc, schema, str_result);
- str_result.append(";");
-
- str_result.append("U");
- str_result.append(std::to_string(qc.cols_used()));
-
- return str_result;
-}
-
-void WriteQueryConstraintsToMetatrace(metatrace::Record* r,
- const QueryConstraints& qc,
- const SqliteTableLegacy::Schema& schema) {
- r->AddArg("constraint_count", std::to_string(qc.constraints().size()));
- std::string constraints;
- ConstraintsToString(qc, schema, constraints);
- r->AddArg("constraints", constraints);
- r->AddArg("order_by_count", std::to_string(qc.order_by().size()));
- std::string order_by;
- OrderByToString(qc, schema, order_by);
- r->AddArg("order_by", order_by);
- r->AddArg("columns_used", std::to_string(qc.cols_used()));
-}
-
-} // namespace
-
-// static
-bool SqliteTableLegacy::debug = false;
-
-SqliteTableLegacy::SqliteTableLegacy() = default;
-SqliteTableLegacy::~SqliteTableLegacy() = default;
-
-base::Status SqliteTableLegacy::ModifyConstraints(QueryConstraints*) {
- return base::OkStatus();
-}
-
-int SqliteTableLegacy::FindFunction(const char*, FindFunctionFn*, void**) {
- return 0;
-}
-
-base::Status SqliteTableLegacy::Update(int, sqlite3_value**, sqlite3_int64*) {
- return base::ErrStatus("Updating not supported");
-}
-
-bool SqliteTableLegacy::ReadConstraints(int idxNum,
- const char* idxStr,
- int argc) {
- bool cache_hit = true;
- if (idxNum != qc_hash_) {
- qc_cache_ = QueryConstraints::FromString(idxStr);
- qc_hash_ = idxNum;
- cache_hit = false;
- }
-
- PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED,
- "SQLITE_TABLE_READ_CONSTRAINTS", [&](metatrace::Record* r) {
- r->AddArg("cache_hit", std::to_string(cache_hit));
- r->AddArg("name", name_);
- WriteQueryConstraintsToMetatrace(r, qc_cache_, schema_);
- r->AddArg("raw_constraints", idxStr);
- r->AddArg("argc", std::to_string(argc));
- });
-
- // Logging this every ReadConstraints just leads to log spam on joins making
- // it unusable. Instead, only print this out when we miss the cache (which
- // happens precisely when the constraint set from SQLite changes.)
- if (SqliteTableLegacy::debug && !cache_hit) {
- PERFETTO_LOG("[%s::ParseConstraints] constraints=%s argc=%d", name_.c_str(),
- QcDebugStr(qc_cache_, schema_).c_str(), argc);
- }
- return cache_hit;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// SqliteTableLegacy::BaseCursor implementation
-////////////////////////////////////////////////////////////////////////////////
-
-SqliteTableLegacy::BaseCursor::BaseCursor(SqliteTableLegacy* table)
- : table_(table) {
- // This is required to prevent us from leaving this field uninitialised if
- // we ever move construct the Cursor.
- pVtab = table;
-}
-SqliteTableLegacy::BaseCursor::~BaseCursor() = default;
-
-////////////////////////////////////////////////////////////////////////////////
-// SqliteTableLegacy::Column implementation
-////////////////////////////////////////////////////////////////////////////////
-
-SqliteTableLegacy::Column::Column(size_t index,
- std::string name,
- SqlValue::Type type,
- bool hidden)
- : index_(index), name_(name), type_(type), hidden_(hidden) {}
-
-////////////////////////////////////////////////////////////////////////////////
-// SqliteTableLegacy::Schema implementation
-////////////////////////////////////////////////////////////////////////////////
-
-SqliteTableLegacy::Schema::Schema() = default;
-
-SqliteTableLegacy::Schema::Schema(std::vector<Column> columns,
- std::vector<size_t> primary_keys)
- : columns_(std::move(columns)), primary_keys_(std::move(primary_keys)) {
- for (size_t i = 0; i < columns_.size(); i++) {
- PERFETTO_CHECK(columns_[i].index() == i);
- }
- for (auto key : primary_keys_) {
- PERFETTO_CHECK(key < columns_.size());
- }
-}
-
-SqliteTableLegacy::Schema::Schema(const Schema&) = default;
-SqliteTableLegacy::Schema& SqliteTableLegacy::Schema::operator=(const Schema&) =
- default;
-
-std::string SqliteTableLegacy::Schema::ToCreateTableStmt() const {
- std::string stmt = "CREATE TABLE x(";
- for (size_t i = 0; i < columns_.size(); ++i) {
- const Column& col = columns_[i];
- stmt += " " + col.name();
-
- if (col.type() != SqlValue::Type::kNull) {
- stmt += " " + sqlite::utils::SqlValueTypeToString(col.type());
- } else if (std::find(primary_keys_.begin(), primary_keys_.end(), i) !=
- primary_keys_.end()) {
- PERFETTO_FATAL("Unknown type for primary key column %s",
- col.name().c_str());
- }
- if (col.hidden()) {
- stmt += " HIDDEN";
- }
- stmt += ",";
- }
- stmt += " PRIMARY KEY(";
- for (size_t i = 0; i < primary_keys_.size(); i++) {
- if (i != 0)
- stmt += ", ";
- stmt += columns_[primary_keys_[i]].name();
- }
- stmt += ")) WITHOUT ROWID;";
- return stmt;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// TypedSqliteTableBase implementation
-////////////////////////////////////////////////////////////////////////////////
-
-TypedSqliteTableBase::~TypedSqliteTableBase() = default;
-
-base::Status TypedSqliteTableBase::DeclareAndAssignVtab(
- std::unique_ptr<SqliteTableLegacy> table,
- sqlite3_vtab** tab) {
- auto create_stmt = table->schema().ToCreateTableStmt();
- PERFETTO_DLOG("Create table statement: %s", create_stmt.c_str());
- RETURN_IF_ERROR(table->engine_->DeclareVirtualTable(create_stmt));
- *tab = table.release();
- return base::OkStatus();
-}
-
-int TypedSqliteTableBase::xDestroy(sqlite3_vtab* t) {
- auto* table = static_cast<SqliteTableLegacy*>(t);
- table->engine_->OnSqliteTableDestroyed(table->name_);
- delete table;
- return SQLITE_OK;
-}
-
-int TypedSqliteTableBase::xDestroyFatal(sqlite3_vtab*) {
- PERFETTO_FATAL("xDestroy should not be called");
-}
-
-int TypedSqliteTableBase::xConnectRestoreTable(sqlite3*,
- void* arg,
- int,
- const char* const* argv,
- sqlite3_vtab** tab,
- char** pzErr) {
- auto* xArg = static_cast<BaseModuleArg*>(arg);
-
- // SQLite guarantees that argv[2] contains the name of the table.
- std::string table_name = argv[2];
- base::StatusOr<std::unique_ptr<SqliteTableLegacy>> table =
- xArg->engine->RestoreSqliteTable(table_name);
- if (!table.status().ok()) {
- *pzErr = sqlite3_mprintf("%s", table.status().c_message());
- return SQLITE_ERROR;
- }
- base::Status status = DeclareAndAssignVtab(std::move(table.value()), tab);
- if (!status.ok()) {
- *pzErr = sqlite3_mprintf("%s", status.c_message());
- return SQLITE_ERROR;
- }
- return SQLITE_OK;
-}
-
-int TypedSqliteTableBase::xDisconnectSaveTable(sqlite3_vtab* t) {
- auto* table = static_cast<TypedSqliteTableBase*>(t);
- base::Status status = table->engine_->SaveSqliteTable(
- table->name(), std::unique_ptr<SqliteTableLegacy>(table));
- return table->SetStatusAndReturn(status);
-}
-
-base::Status TypedSqliteTableBase::InitInternal(SqliteEngine* engine,
- int argc,
- const char* const* argv) {
- // Set the engine to allow saving into it later.
- engine_ = engine;
-
- // SQLite guarantees that argv[0] will be the "module" name: this is the
- // same as |table_name| passed to the Register function.
- module_name_ = argv[0];
-
- // SQLite guarantees that argv[2] contains the name of the table: for
- // non-arg taking tables, this will be the same as |table_name| but for
- // arg-taking tables, this will be the table name as defined by the
- // user in the CREATE VIRTUAL TABLE call.
- name_ = argv[2];
-
- Schema schema;
- RETURN_IF_ERROR(Init(argc, argv, &schema));
- schema_ = std::move(schema);
- return base::OkStatus();
-}
-
-int TypedSqliteTableBase::xOpen(sqlite3_vtab* t,
- sqlite3_vtab_cursor** ppCursor) {
- auto* table = static_cast<TypedSqliteTableBase*>(t);
- *ppCursor =
- static_cast<sqlite3_vtab_cursor*>(table->CreateCursor().release());
- return SQLITE_OK;
-}
-
-int TypedSqliteTableBase::xBestIndex(sqlite3_vtab* t, sqlite3_index_info* idx) {
- auto* table = static_cast<TypedSqliteTableBase*>(t);
-
- QueryConstraints qc(idx->colUsed);
-
- for (int i = 0; i < idx->nConstraint; i++) {
- const auto& cs = idx->aConstraint[i];
- if (!cs.usable)
- continue;
- qc.AddConstraint(cs.iColumn, cs.op, i);
- }
-
- for (int i = 0; i < idx->nOrderBy; i++) {
- int column = idx->aOrderBy[i].iColumn;
- bool desc = idx->aOrderBy[i].desc;
- qc.AddOrderBy(column, desc);
- }
-
- int ret = table->SetStatusAndReturn(table->ModifyConstraints(&qc));
- if (ret != SQLITE_OK)
- return ret;
-
- BestIndexInfo info;
- info.estimated_cost = idx->estimatedCost;
- info.estimated_rows = idx->estimatedRows;
- info.sqlite_omit_constraint.resize(qc.constraints().size());
-
- ret = table->BestIndex(qc, &info);
-
- if (ret != SQLITE_OK)
- return ret;
-
- idx->orderByConsumed = qc.order_by().empty() || info.sqlite_omit_order_by;
- idx->estimatedCost = info.estimated_cost;
- idx->estimatedRows = info.estimated_rows;
-
- // First pass: mark all constraints as omitted to ensure that any pruned
- // constraints are not checked for by SQLite.
- for (int i = 0; i < idx->nConstraint; ++i) {
- auto& u = idx->aConstraintUsage[i];
- u.omit = true;
- }
-
- // Second pass: actually set the correct omit and index values for all
- // retained constraints.
- for (uint32_t i = 0; i < qc.constraints().size(); ++i) {
- auto& u = idx->aConstraintUsage[qc.constraints()[i].a_constraint_idx];
- u.omit = info.sqlite_omit_constraint[i];
- u.argvIndex = static_cast<int>(i) + 1;
- }
-
- PERFETTO_TP_TRACE(
- metatrace::Category::QUERY_TIMELINE, "SQLITE_TABLE_BEST_INDEX",
- [&](metatrace::Record* r) {
- r->AddArg("name", table->name());
- WriteQueryConstraintsToMetatrace(r, qc, table->schema());
- r->AddArg("order_by_consumed", std::to_string(idx->orderByConsumed));
- r->AddArg("estimated_cost", std::to_string(idx->estimatedCost));
- r->AddArg("estimated_rows",
- std::to_string(static_cast<int64_t>(idx->estimatedRows)));
- });
-
- auto out_qc_str = qc.ToNewSqlite3String();
- if (SqliteTableLegacy::debug) {
- PERFETTO_LOG(
- "[%s::BestIndex] constraints=%s orderByConsumed=%d estimatedCost=%f "
- "estimatedRows=%" PRId64,
- table->name().c_str(), QcDebugStr(qc, table->schema()).c_str(),
- idx->orderByConsumed, idx->estimatedCost,
- static_cast<int64_t>(idx->estimatedRows));
- }
-
- idx->idxStr = out_qc_str.release();
- idx->needToFreeIdxStr = true;
- idx->idxNum = ++table->best_index_num_;
-
- return SQLITE_OK;
-}
-
-} // namespace trace_processor
-} // namespace perfetto
diff --git a/src/trace_processor/sqlite/sqlite_table.h b/src/trace_processor/sqlite/sqlite_table.h
deleted file mode 100644
index d3b1c32..0000000
--- a/src/trace_processor/sqlite/sqlite_table.h
+++ /dev/null
@@ -1,435 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed 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.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_SQLITE_SQLITE_TABLE_H_
-#define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_TABLE_H_
-
-#include <sqlite3.h>
-#include <memory>
-#include <string>
-#include <type_traits>
-#include <vector>
-
-#include "perfetto/base/status.h"
-#include "perfetto/trace_processor/basic_types.h"
-#include "src/trace_processor/sqlite/query_constraints.h"
-
-namespace perfetto::trace_processor {
-
-class SqliteEngine;
-class TypedSqliteTableBase;
-
-// Abstract base class representing a SQLite virtual table. Implements the
-// common bookeeping required across all tables and allows subclasses to
-// implement a friendlier API than that required by SQLite.
-class SqliteTableLegacy : public sqlite3_vtab {
- public:
- // Custom opcodes used by subclasses of SqliteTableLegacy.
- // Stored here as we need a central repository of opcodes to prevent clashes
- // between different sub-classes.
- enum CustomFilterOpcode {
- kSourceGeqOpCode = SQLITE_INDEX_CONSTRAINT_FUNCTION + 1,
- };
- // Describes a column of this table.
- class Column {
- public:
- Column(size_t idx,
- std::string name,
- SqlValue::Type type,
- bool hidden = false);
-
- size_t index() const { return index_; }
- const std::string& name() const { return name_; }
- SqlValue::Type type() const { return type_; }
-
- bool hidden() const { return hidden_; }
- void set_hidden(bool hidden) { hidden_ = hidden; }
-
- private:
- size_t index_ = 0;
- std::string name_;
- SqlValue::Type type_ = SqlValue::Type::kNull;
- bool hidden_ = false;
- };
-
- // Abstract base class representing an SQLite Cursor. Presents a friendlier
- // API for subclasses to implement.
- class BaseCursor : public sqlite3_vtab_cursor {
- public:
- // Enum for the history of calls to Filter.
- enum class FilterHistory : uint32_t {
- // Indicates that constraint set passed is the different to the
- // previous Filter call.
- kDifferent = 0,
-
- // Indicates that the constraint set passed is the same as the previous
- // Filter call.
- // This can be useful for subclasses to perform optimizations on repeated
- // nested subqueries.
- kSame = 1,
- };
-
- explicit BaseCursor(SqliteTableLegacy* table);
- virtual ~BaseCursor();
-
- // Methods to be implemented by derived table classes.
- // Note: these methods are intentionally not virtual for performance
- // reasons. As these methods are not defined, there will be compile errors
- // thrown if any of these methods are missing.
-
- // Called to intialise the cursor with the constraints of the query.
- base::Status Filter(const QueryConstraints& qc,
- sqlite3_value**,
- FilterHistory);
-
- // Called to forward the cursor to the next row in the table.
- void Next();
-
- // Called to check if the cursor has reached eof. Column will be called iff
- // this method returns true.
- bool Eof();
-
- // Used to extract the value from the column at index |N|.
- void Column(sqlite3_context* context, int N);
-
- SqliteTableLegacy* table() const { return table_; }
-
- protected:
- BaseCursor(BaseCursor&) = delete;
- BaseCursor& operator=(const BaseCursor&) = delete;
-
- BaseCursor(BaseCursor&&) noexcept = default;
- BaseCursor& operator=(BaseCursor&&) = default;
-
- private:
- SqliteTableLegacy* table_ = nullptr;
- };
-
- // The schema of the table. Created by subclasses to allow the table class to
- // do filtering and inform SQLite about the CREATE table statement.
- class Schema {
- public:
- Schema();
- Schema(std::vector<Column>, std::vector<size_t> primary_keys);
-
- // This class is explicitly copiable.
- Schema(const Schema&);
- Schema& operator=(const Schema& t);
-
- std::string ToCreateTableStmt() const;
-
- const std::vector<Column>& columns() const { return columns_; }
- std::vector<Column>* mutable_columns() { return &columns_; }
-
- const std::vector<size_t> primary_keys() { return primary_keys_; }
-
- private:
- // The names and types of the columns of the table.
- std::vector<Column> columns_;
-
- // The primary keys of the table given by an offset into |columns|.
- std::vector<size_t> primary_keys_;
- };
-
- enum TableType {
- // A table which automatically exists in the main schema and cannot be
- // created with CREATE VIRTUAL TABLE.
- // Note: the name value here matches the naming in the vtable docs of
- // SQLite.
- kEponymousOnly,
-
- // A table which must be explicitly created using a CREATE VIRTUAL TABLE
- // statement (i.e. does exist automatically).
- kExplicitCreate,
- };
-
- // Public for unique_ptr destructor calls.
- virtual ~SqliteTableLegacy();
-
- // When set it logs all BestIndex and Filter actions on the console.
- static bool debug;
-
- protected:
- // Populated by a BestIndex call to allow subclasses to tweak SQLite's
- // handling of sets of constraints.
- struct BestIndexInfo {
- // Contains bools which indicate whether SQLite should omit double checking
- // the constraint at that index.
- //
- // If there are no constraints, SQLite will be told it can omit checking for
- // the whole query.
- std::vector<bool> sqlite_omit_constraint;
-
- // Indicates that SQLite should not double check the result of the order by
- // clause.
- //
- // If there are no order by clauses, this value will be ignored and SQLite
- // will be told that it can omit double checking (i.e. this value will
- // implicitly be taken to be true).
- bool sqlite_omit_order_by = false;
-
- // Stores the estimated cost of this query.
- double estimated_cost = 0;
-
- // Estimated row count.
- int64_t estimated_rows = 0;
- };
-
- SqliteTableLegacy();
-
- // Methods to be implemented by derived table classes.
- virtual base::Status Init(int argc, const char* const* argv, Schema*) = 0;
- virtual std::unique_ptr<BaseCursor> CreateCursor() = 0;
- virtual int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) = 0;
-
- // Optional metods to implement.
- using FindFunctionFn = void (*)(sqlite3_context*, int, sqlite3_value**);
- virtual base::Status ModifyConstraints(QueryConstraints* qc);
- virtual int FindFunction(const char* name, FindFunctionFn* fn, void** args);
-
- // At registration time, the function should also pass true for |read_write|.
- virtual base::Status Update(int, sqlite3_value**, sqlite3_int64*);
-
- bool ReadConstraints(int idxNum, const char* idxStr, int argc);
-
- const Schema& schema() const { return schema_; }
- const std::string& module_name() const { return module_name_; }
- const std::string& name() const { return name_; }
-
- private:
- template <typename, typename>
- friend class TypedSqliteTable;
- friend class TypedSqliteTableBase;
-
- SqliteTableLegacy(const SqliteTableLegacy&) = delete;
- SqliteTableLegacy& operator=(const SqliteTableLegacy&) = delete;
-
- // The engine class this table is registered with. Used for restoring/saving
- // the table.
- SqliteEngine* engine_ = nullptr;
-
- // This name of the table. For tables created using CREATE VIRTUAL TABLE, this
- // will be the name of the table specified by the query. For automatically
- // created tables, this will be the same as the module name registered.
- std::string name_;
-
- // The module name is the name that will be registered. This is
- // differs from the table name (|name_|) where the table was created using
- // CREATE VIRTUAL TABLE.
- std::string module_name_;
-
- Schema schema_;
-
- QueryConstraints qc_cache_;
- int qc_hash_ = 0;
- int best_index_num_ = 0;
-};
-
-class TypedSqliteTableBase : public SqliteTableLegacy {
- protected:
- struct BaseModuleArg {
- sqlite3_module module;
- SqliteEngine* engine;
- TableType table_type;
- };
-
- ~TypedSqliteTableBase() override;
-
- static int xDestroy(sqlite3_vtab*);
- static int xDestroyFatal(sqlite3_vtab*);
-
- static int xConnectRestoreTable(sqlite3* xdb,
- void* arg,
- int argc,
- const char* const* argv,
- sqlite3_vtab** tab,
- char** pzErr);
- static int xDisconnectSaveTable(sqlite3_vtab*);
-
- static int xOpen(sqlite3_vtab*, sqlite3_vtab_cursor**);
- static int xBestIndex(sqlite3_vtab*, sqlite3_index_info*);
-
- static base::Status DeclareAndAssignVtab(
- std::unique_ptr<SqliteTableLegacy> table,
- sqlite3_vtab** tab);
-
- base::Status InitInternal(SqliteEngine* engine,
- int argc,
- const char* const* argv);
-
- int SetStatusAndReturn(base::Status status) {
- if (!status.ok()) {
- sqlite3_free(zErrMsg);
- zErrMsg = sqlite3_mprintf("%s", status.c_message());
- return SQLITE_ERROR;
- }
- return SQLITE_OK;
- }
-};
-
-template <typename SubTable, typename Context>
-class TypedSqliteTable : public TypedSqliteTableBase {
- public:
- struct ModuleArg : public BaseModuleArg {
- Context context;
- };
-
- static std::unique_ptr<ModuleArg> CreateModuleArg(SqliteEngine* engine,
- Context ctx,
- TableType table_type,
- bool updatable) {
- auto arg = std::make_unique<ModuleArg>();
- arg->module = CreateModule(table_type, updatable);
- arg->engine = engine;
- arg->table_type = table_type;
- arg->context = std::move(ctx);
- return arg;
- }
-
- private:
- static constexpr sqlite3_module CreateModule(TableType table_type,
- bool updatable) {
- sqlite3_module module;
- memset(&module, 0, sizeof(sqlite3_module));
- switch (table_type) {
- case TableType::kEponymousOnly:
- // Neither xCreate nor xDestroy should ever be called for
- // eponymous-only tables.
- module.xCreate = nullptr;
- module.xDestroy = &xDestroyFatal;
-
- // xConnect and xDisconnect will automatically be called with
- // |module_name| == |name|.
- module.xConnect = &xCreate;
- module.xDisconnect = &xDestroy;
- break;
- case TableType::kExplicitCreate:
- // xCreate and xDestroy will be called when the table is CREATE-ed and
- // DROP-ed respectively.
- module.xCreate = &xCreate;
- module.xDestroy = &xDestroy;
-
- // xConnect and xDisconnect can be called at any time.
- module.xConnect = &xConnectRestoreTable;
- module.xDisconnect = &xDisconnectSaveTable;
- break;
- }
- module.xOpen = &xOpen;
- module.xClose = &xClose;
- module.xBestIndex = &xBestIndex;
- module.xFindFunction = &xFindFunction;
- module.xFilter = &xFilter;
- module.xNext = &xNext;
- module.xEof = &xEof;
- module.xColumn = &xColumn;
- module.xRowid = &xRowid;
- if (updatable) {
- module.xUpdate = &xUpdate;
- }
- return module;
- }
-
- static int xCreate(sqlite3* xdb,
- void* arg,
- int argc,
- const char* const* argv,
- sqlite3_vtab** tab,
- char** pzErr) {
- auto* xdesc = static_cast<ModuleArg*>(arg);
- std::unique_ptr<SubTable> table(new SubTable(xdb, &*xdesc->context));
- SubTable* table_ptr = table.get();
- base::Status status = table->InitInternal(xdesc->engine, argc, argv);
- if (!status.ok()) {
- *pzErr = sqlite3_mprintf("%s", status.c_message());
- return SQLITE_ERROR;
- }
- status = DeclareAndAssignVtab(std::move(table), tab);
- if (!status.ok()) {
- *pzErr = sqlite3_mprintf("%s", status.c_message());
- return SQLITE_ERROR;
- }
- xdesc->engine->OnSqliteTableCreated(table_ptr->name(), xdesc->table_type);
- return SQLITE_OK;
- }
- static int xClose(sqlite3_vtab_cursor* c) {
- delete static_cast<typename SubTable::Cursor*>(c);
- return SQLITE_OK;
- }
- static int xFindFunction(sqlite3_vtab* t,
- int,
- const char* name,
- void (**fn)(sqlite3_context*, int, sqlite3_value**),
- void** args) {
- return static_cast<SubTable*>(t)->FindFunction(name, fn, args);
- }
- static int xFilter(sqlite3_vtab_cursor* vc,
- int i,
- const char* s,
- int a,
- sqlite3_value** v) {
- auto* cursor = static_cast<typename SubTable::Cursor*>(vc);
- bool is_cached = cursor->table()->ReadConstraints(i, s, a);
- auto history = is_cached ? BaseCursor::FilterHistory::kSame
- : BaseCursor::FilterHistory::kDifferent;
- auto* table = static_cast<SubTable*>(cursor->table());
- return table->SetStatusAndReturn(
- cursor->Filter(cursor->table()->qc_cache_, v, history));
- }
- static int xNext(sqlite3_vtab_cursor* c) {
- auto* cursor = static_cast<typename SubTable::Cursor*>(c);
- using NextType = decltype(&SubTable::Cursor::Next);
- using ReturnType =
- std::invoke_result_t<NextType, typename SubTable::Cursor*>;
- if constexpr (std::is_same_v<ReturnType, void>) {
- cursor->Next();
- return SQLITE_OK;
- } else {
- auto* table = static_cast<SubTable*>(cursor->table());
- return table->SetStatusAndReturn(cursor->Next());
- }
- }
- static int xEof(sqlite3_vtab_cursor* c) {
- return static_cast<int>(static_cast<typename SubTable::Cursor*>(c)->Eof());
- }
- static int xColumn(sqlite3_vtab_cursor* c, sqlite3_context* a, int b) {
- auto* cursor = static_cast<typename SubTable::Cursor*>(c);
- using ColumnType = decltype(&SubTable::Cursor::Column);
- using ReturnType =
- std::invoke_result_t<ColumnType, typename SubTable::Cursor*,
- sqlite3_context*, int>;
- if constexpr (std::is_same_v<ReturnType, void>) {
- cursor->Column(a, b);
- return SQLITE_OK;
- } else {
- auto* table = static_cast<SubTable*>(cursor->table());
- return table->SetStatusAndReturn(cursor->Column(a, b));
- }
- }
- static int xRowid(sqlite3_vtab_cursor*, sqlite3_int64*) {
- return SQLITE_ERROR;
- }
- static int xUpdate(sqlite3_vtab* t,
- int a,
- sqlite3_value** v,
- sqlite3_int64* r) {
- auto* table = static_cast<SubTable*>(t);
- return table->SetStatusAndReturn(table->Update(a, v, r));
- }
-};
-
-} // namespace perfetto::trace_processor
-
-#endif // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_TABLE_H_
diff --git a/src/trace_processor/sqlite/sqlite_utils.cc b/src/trace_processor/sqlite/sqlite_utils.cc
index 0de36f6..b9141df 100644
--- a/src/trace_processor/sqlite/sqlite_utils.cc
+++ b/src/trace_processor/sqlite/sqlite_utils.cc
@@ -31,7 +31,6 @@
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/sqlite/scoped_db.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
namespace perfetto::trace_processor::sqlite::utils {
namespace internal {
diff --git a/src/trace_processor/sqlite/sqlite_utils.h b/src/trace_processor/sqlite/sqlite_utils.h
index ba1eed6..77654e3 100644
--- a/src/trace_processor/sqlite/sqlite_utils.h
+++ b/src/trace_processor/sqlite/sqlite_utils.h
@@ -22,6 +22,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
+#include <functional>
#include <optional>
#include <string>
#include <utility>
@@ -32,7 +33,6 @@
#include "perfetto/ext/base/status_or.h"
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
namespace perfetto::trace_processor::sqlite::utils {
@@ -156,6 +156,54 @@
status.c_message()));
}
+// For a given |sqlite3_index_info| struct received in a BestIndex call, returns
+// whether all |arg_count| arguments (with |is_arg_column| indicating whether a
+// given column is a function argument) have exactly one equaltiy constraint
+// associated with them.
+//
+// If so, the associated constraint is omitted and the argvIndex is mapped to
+// the corresponding argument's index.
+inline base::Status ValidateFunctionArguments(
+ sqlite3_index_info* info,
+ size_t arg_count,
+ const std::function<bool(size_t)>& is_arg_column) {
+ std::vector<bool> present;
+ size_t present_count = 0;
+ for (int i = 0; i < info->nConstraint; ++i) {
+ const auto& in = info->aConstraint[i];
+ if (!in.usable) {
+ continue;
+ }
+ auto cs_col = static_cast<size_t>(in.iColumn);
+ if (!is_arg_column(cs_col)) {
+ continue;
+ }
+ if (!IsOpEq(in.op)) {
+ return base::ErrStatus(
+ "Unexpected non equality constraints for column %zu", cs_col);
+ }
+ if (cs_col >= present.size()) {
+ present.resize(cs_col + 1);
+ }
+ if (present[cs_col]) {
+ return base::ErrStatus("Unexpected multiple constraints for column %zu",
+ cs_col);
+ }
+ present[cs_col] = true;
+ present_count++;
+
+ auto& out = info->aConstraintUsage[i];
+ out.argvIndex = static_cast<int>(present_count);
+ out.omit = true;
+ }
+ if (present_count != arg_count) {
+ return base::ErrStatus(
+ "Unexpected missing argument: expected %zu, actual %zu", arg_count,
+ present_count);
+ }
+ return base::OkStatus();
+}
+
// Converts the given SqlValue type to the type string SQLite understands.
inline std::string SqlValueTypeToString(SqlValue::Type type) {
switch (type) {
diff --git a/src/trace_processor/trace_processor.cc b/src/trace_processor/trace_processor.cc
index 4d022f7..060c16d 100644
--- a/src/trace_processor/trace_processor.cc
+++ b/src/trace_processor/trace_processor.cc
@@ -16,11 +16,12 @@
#include "perfetto/trace_processor/trace_processor.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
+#include <memory>
+
+#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/trace_processor_impl.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
TraceProcessor::MetatraceConfig::MetatraceConfig() = default;
@@ -32,12 +33,4 @@
TraceProcessor::~TraceProcessor() = default;
-// static
-void EnableSQLiteVtableDebugging() {
- // This level of indirection is required to avoid clients to depend on table.h
- // which in turn requires sqlite headers.
- SqliteTableLegacy::debug = true;
-}
-
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 9f53c22..b36a63a 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -103,7 +103,6 @@
#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sql_stats_table.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
#include "src/trace_processor/sqlite/stats_table.h"
#include "src/trace_processor/storage/metadata.h"
#include "src/trace_processor/storage/trace_storage.h"
@@ -799,7 +798,6 @@
RegisterStaticTable(storage->slice_table());
RegisterStaticTable(storage->flow_table());
- RegisterStaticTable(storage->slice_table());
RegisterStaticTable(storage->sched_slice_table());
RegisterStaticTable(storage->spurious_sched_wakeup_table());
RegisterStaticTable(storage->thread_state_table());
@@ -866,9 +864,6 @@
RegisterStaticTable(storage->jit_code_table());
RegisterStaticTable(storage->jit_frame_table());
- RegisterStaticTable(storage->jit_code_table());
- RegisterStaticTable(storage->jit_frame_table());
-
RegisterStaticTable(storage->surfaceflinger_layers_snapshot_table());
RegisterStaticTable(storage->surfaceflinger_layer_table());
RegisterStaticTable(storage->surfaceflinger_transactions_table());
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 692b105..faabf21 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -813,7 +813,6 @@
static const option long_options[] = {
{"help", no_argument, nullptr, 'h'},
{"version", no_argument, nullptr, 'v'},
- {"debug", no_argument, nullptr, 'd'},
{"wide", no_argument, nullptr, 'W'},
{"perf-file", required_argument, nullptr, 'p'},
{"query-file", required_argument, nullptr, 'q'},
@@ -859,11 +858,6 @@
exit(0);
}
- if (option == 'd') {
- EnableSQLiteVtableDebugging();
- continue;
- }
-
if (option == 'W') {
command_line_options.wide = true;
continue;
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index 4e93f6a..bd2e925 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -32,6 +32,8 @@
"build_timeline.h",
"filter_ftrace_using_allowlist.cc",
"filter_ftrace_using_allowlist.h",
+ "filter_print_events.cc",
+ "filter_print_events.h",
"filter_sched_waking_events.cc",
"filter_sched_waking_events.h",
"filter_task_rename.cc",
@@ -52,6 +54,8 @@
"redact_sched_switch.h",
"scrub_ftrace_events.cc",
"scrub_ftrace_events.h",
+ "scrub_process_stats.cc",
+ "scrub_process_stats.h",
"scrub_process_trees.cc",
"scrub_process_trees.h",
"scrub_trace_packet.cc",
@@ -83,6 +87,7 @@
"filter_task_rename_integrationtest.cc",
"redact_sched_switch_integrationtest.cc",
"scrub_ftrace_events_integrationtest.cc",
+ "scrub_process_stats_integrationtest.cc",
"scrub_process_trees_integrationtest.cc",
"trace_redaction_integration_fixture.cc",
"trace_redaction_integration_fixture.h",
diff --git a/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc b/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc
index c0abf3f..89946ae 100644
--- a/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc
+++ b/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc
@@ -170,6 +170,7 @@
ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kTimestampFieldNumber));
// These are events.
+ ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kPrintFieldNumber));
ASSERT_TRUE(
events.count(protos::pbzero::FtraceEvent::kCpuFrequencyFieldNumber));
ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kCpuIdleFieldNumber));
@@ -197,7 +198,6 @@
// These are events.
ASSERT_FALSE(
events.count(protos::pbzero::FtraceEvent::kOomScoreAdjUpdateFieldNumber));
- ASSERT_FALSE(events.count(protos::pbzero::FtraceEvent::kPrintFieldNumber));
ASSERT_FALSE(
events.count(protos::pbzero::FtraceEvent::kSchedProcessExitFieldNumber));
ASSERT_FALSE(
diff --git a/src/trace_redaction/filter_print_events.cc b/src/trace_redaction/filter_print_events.cc
new file mode 100644
index 0000000..6c1843b
--- /dev/null
+++ b/src/trace_redaction/filter_print_events.cc
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed 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.
+ */
+
+#include "src/trace_redaction/filter_print_events.h"
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+base::Status FilterPrintEvents::VerifyContext(const Context& context) const {
+ if (!context.package_uid.has_value()) {
+ return base::ErrStatus("FilterPrintEvents: missing packet uid.");
+ }
+
+ if (!context.timeline) {
+ return base::ErrStatus("FilterPrintEvents: missing timeline.");
+ }
+
+ return base::OkStatus();
+}
+
+bool FilterPrintEvents::KeepEvent(const Context& context,
+ protozero::ConstBytes bytes) const {
+ PERFETTO_DCHECK(context.timeline);
+ PERFETTO_DCHECK(context.package_uid.has_value());
+
+ const auto* timeline = context.timeline.get();
+ auto package_uid = context.package_uid;
+
+ protozero::ProtoDecoder event(bytes);
+
+ // This is not a print packet. Keep the packet.
+ if (!event.FindField(protos::pbzero::FtraceEvent::kPrintFieldNumber)
+ .valid()) {
+ return true;
+ }
+
+ auto time =
+ event.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber);
+ auto pid = event.FindField(protos::pbzero::FtraceEvent::kPidFieldNumber);
+
+ // Pid + Time --> UID, if the uid matches the target package, keep the event.
+ return pid.valid() && time.valid() &&
+ timeline->Search(time.as_uint64(), pid.as_int32()).uid == package_uid;
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/filter_print_events.h b/src/trace_redaction/filter_print_events.h
new file mode 100644
index 0000000..36ef92b
--- /dev/null
+++ b/src/trace_redaction/filter_print_events.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed 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.
+ */
+
+#ifndef SRC_TRACE_REDACTION_FILTER_PRINT_EVENTS_H_
+#define SRC_TRACE_REDACTION_FILTER_PRINT_EVENTS_H_
+
+#include "perfetto/protozero/field.h"
+#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+// event {
+// timestamp: 6702093749982230
+// pid: 7947 <-- target
+// print {
+// buf: "B|7105|virtual void
+// swappy::ChoreographerThread::onChoreographer()\n"
+// }
+// }
+//
+// If the target pid doesn't belong to the target package (context.package_uid),
+// then the event will be marked as "don't keep".
+class FilterPrintEvents : public FtraceEventFilter {
+ public:
+ base::Status VerifyContext(const Context& context) const override;
+ bool KeepEvent(const Context& context,
+ protozero::ConstBytes bytes) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_FILTER_PRINT_EVENTS_H_
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index 5a55943..961e337 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -18,6 +18,7 @@
#include "perfetto/base/status.h"
#include "src/trace_redaction/build_timeline.h"
#include "src/trace_redaction/filter_ftrace_using_allowlist.h"
+#include "src/trace_redaction/filter_print_events.h"
#include "src/trace_redaction/filter_sched_waking_events.h"
#include "src/trace_redaction/filter_task_rename.h"
#include "src/trace_redaction/find_package_uid.h"
@@ -26,6 +27,7 @@
#include "src/trace_redaction/prune_package_list.h"
#include "src/trace_redaction/redact_sched_switch.h"
#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/scrub_process_stats.h"
#include "src/trace_redaction/scrub_process_trees.h"
#include "src/trace_redaction/scrub_trace_packet.h"
#include "src/trace_redaction/trace_redaction_framework.h"
@@ -55,11 +57,13 @@
// number of events they need to iterate over.
auto scrub_ftrace_events = redactor.emplace_transform<ScrubFtraceEvents>();
scrub_ftrace_events->emplace_back<FilterFtraceUsingAllowlist>();
+ scrub_ftrace_events->emplace_back<FilterPrintEvents>();
scrub_ftrace_events->emplace_back<FilterSchedWakingEvents>();
scrub_ftrace_events->emplace_back<FilterTaskRename>();
redactor.emplace_transform<ScrubProcessTrees>();
redactor.emplace_transform<RedactSchedSwitch>();
+ redactor.emplace_transform<ScrubProcessStats>();
Context context;
context.package_name = package_name;
diff --git a/src/trace_redaction/populate_allow_lists.cc b/src/trace_redaction/populate_allow_lists.cc
index 2ba81b6..7a5b48a 100644
--- a/src/trace_redaction/populate_allow_lists.cc
+++ b/src/trace_redaction/populate_allow_lists.cc
@@ -77,6 +77,7 @@
protos::pbzero::FtraceEvent::kIonBufferDestroyFieldNumber,
protos::pbzero::FtraceEvent::kDmaHeapStatFieldNumber,
protos::pbzero::FtraceEvent::kRssStatThrottledFieldNumber,
+ protos::pbzero::FtraceEvent::kPrintFieldNumber,
};
// TODO: Some ftrace fields should be retained, but they carry too much risk
diff --git a/src/trace_redaction/scrub_ftrace_events.cc b/src/trace_redaction/scrub_ftrace_events.cc
index 3260c71..c3d1abf 100644
--- a/src/trace_redaction/scrub_ftrace_events.cc
+++ b/src/trace_redaction/scrub_ftrace_events.cc
@@ -94,7 +94,9 @@
bool ScrubFtraceEvents::KeepEvent(const Context& context,
protozero::ConstBytes bytes) const {
for (const auto& filter : filters_) {
- if (!filter->KeepEvent(context, bytes)) {
+ auto keep = filter->KeepEvent(context, bytes);
+
+ if (!keep) {
return false;
}
}
diff --git a/src/trace_redaction/scrub_process_stats.cc b/src/trace_redaction/scrub_process_stats.cc
new file mode 100644
index 0000000..991c02f
--- /dev/null
+++ b/src/trace_redaction/scrub_process_stats.cc
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed 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.
+ */
+
+#include "src/trace_redaction/scrub_process_stats.h"
+
+#include <string>
+
+#include "perfetto/base/status.h"
+#include "perfetto/protozero/field.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/ps/process_stats.pbzero.h"
+#include "src/trace_redaction/proto_util.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+base::Status ScrubProcessStats::Transform(const Context& context,
+ std::string* packet) const {
+ if (!context.package_uid.has_value()) {
+ return base::ErrStatus("FilterProcessStats: missing package uid.");
+ }
+
+ if (!context.timeline) {
+ return base::ErrStatus("FilterProcessStats: missing timeline.");
+ }
+
+ protozero::ProtoDecoder packet_decoder(*packet);
+
+ // Very few packets will have process stats. It's best to avoid
+ // reserialization whenever possible.
+ if (!packet_decoder
+ .FindField(protos::pbzero::TracePacket::kProcessStatsFieldNumber)
+ .valid()) {
+ return base::OkStatus();
+ }
+
+ protozero::HeapBuffered<protos::pbzero::TracePacket> message;
+
+ // TODO(vaage): Add primitive to drop all packets that don't have a
+ // timestamp, allowing all other packets assume there are timestamps.
+ auto time_field = packet_decoder.FindField(
+ protos::pbzero::TracePacket::kTimestampFieldNumber);
+ PERFETTO_DCHECK(time_field.valid());
+ auto time = time_field.as_uint64();
+
+ auto* timeline = context.timeline.get();
+ auto uid = context.package_uid.value();
+
+ for (auto packet_field = packet_decoder.ReadField(); packet_field.valid();
+ packet_field = packet_decoder.ReadField()) {
+ if (packet_field.id() !=
+ protos::pbzero::TracePacket::kProcessStatsFieldNumber) {
+ proto_util::AppendField(packet_field, message.get());
+ continue;
+ }
+
+ auto process_stats = std::move(packet_field);
+ protozero::ProtoDecoder process_stats_decoder(process_stats.as_bytes());
+
+ auto* process_stats_message = message->set_process_stats();
+
+ for (auto process_stats_field = process_stats_decoder.ReadField();
+ process_stats_field.valid();
+ process_stats_field = process_stats_decoder.ReadField()) {
+ bool keep_field;
+
+ if (process_stats_field.id() ==
+ protos::pbzero::ProcessStats::kProcessesFieldNumber) {
+ protozero::ProtoDecoder process_decoder(process_stats_field.as_bytes());
+ auto pid = process_decoder.FindField(
+ protos::pbzero::ProcessStats::Process::kPidFieldNumber);
+ keep_field =
+ pid.valid() && timeline->Search(time, pid.as_int32()).uid == uid;
+ } else {
+ keep_field = true;
+ }
+
+ if (keep_field) {
+ proto_util::AppendField(process_stats_field, process_stats_message);
+ }
+ }
+ }
+
+ packet->assign(message.SerializeAsString());
+
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_process_stats.h b/src/trace_redaction/scrub_process_stats.h
new file mode 100644
index 0000000..99b6697
--- /dev/null
+++ b/src/trace_redaction/scrub_process_stats.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed 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.
+ */
+
+#ifndef SRC_TRACE_REDACTION_SCRUB_PROCESS_STATS_H_
+#define SRC_TRACE_REDACTION_SCRUB_PROCESS_STATS_H_
+
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+class ScrubProcessStats : public TransformPrimitive {
+ public:
+ base::Status Transform(const Context& context,
+ std::string* packet) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_SCRUB_PROCESS_STATS_H_
diff --git a/src/trace_redaction/scrub_process_stats_integrationtest.cc b/src/trace_redaction/scrub_process_stats_integrationtest.cc
new file mode 100644
index 0000000..01c61b1
--- /dev/null
+++ b/src/trace_redaction/scrub_process_stats_integrationtest.cc
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed 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.
+ */
+
+#include <cstdint>
+#include <string>
+
+#include "perfetto/base/status.h"
+#include "src/base/test/status_matchers.h"
+#include "src/trace_redaction/build_timeline.h"
+#include "src/trace_redaction/optimize_timeline.h"
+#include "src/trace_redaction/scrub_process_stats.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+#include "src/trace_redaction/trace_redaction_integration_fixture.h"
+#include "src/trace_redaction/trace_redactor.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/ps/process_stats.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+class ScrubProcessStatsTest : public testing::Test,
+ protected TraceRedactionIntegrationFixure {
+ protected:
+ void SetUp() override {
+ trace_redactor()->emplace_collect<BuildTimeline>();
+ trace_redactor()->emplace_build<OptimizeTimeline>();
+ trace_redactor()->emplace_transform<ScrubProcessStats>();
+
+ // Package "com.Unity.com.unity.multiplayer.samples.coop";
+ context()->package_uid = 10252;
+ }
+
+ // Gets pids from all process_stats messages in the trace (bytes).
+ base::FlatSet<int32_t> GetAllPids(const std::string& bytes) const {
+ base::FlatSet<int32_t> pids;
+
+ protos::pbzero::Trace::Decoder decoder(bytes);
+
+ for (auto packet = decoder.packet(); packet; ++packet) {
+ protos::pbzero::TracePacket::Decoder trace_packet(packet->as_bytes());
+
+ if (!trace_packet.has_process_stats()) {
+ continue;
+ }
+
+ protos::pbzero::ProcessStats::Decoder process_stats(
+ trace_packet.process_stats());
+
+ for (auto process = process_stats.processes(); process; ++process) {
+ protos::pbzero::ProcessStats::Process::Decoder p(process->as_bytes());
+ PERFETTO_DCHECK(p.has_pid());
+ pids.insert(p.pid());
+ }
+ }
+
+ return pids;
+ }
+};
+
+// This test is a canary for changes to the test data. If the test data was to
+// change, every test in this file would fail.
+//
+// SELECT DISTINCT pid
+// FROM process
+// WHERE upid IN (
+// SELECT DISTINCT upid
+// FROM counter
+// JOIN process_counter_track ON counter.track_id=process_counter_track.id
+// WHERE name!='oom_score_adj'
+// )
+// ORDER BY pid
+//
+// NOTE: WHERE name!='oom_score_adj' is used because there are two sources for
+// oom_score_adj values and we only want process stats here.
+TEST_F(ScrubProcessStatsTest, VerifyTraceStats) {
+ base::FlatSet<int32_t> expected = {
+ 1, 578, 581, 696, 697, 698, 699, 700, 701, 704,
+ 709, 710, 718, 728, 749, 750, 751, 752, 756, 760,
+ 761, 762, 873, 874, 892, 1046, 1047, 1073, 1074, 1091,
+ 1092, 1093, 1101, 1103, 1104, 1105, 1106, 1107, 1110, 1111,
+ 1112, 1113, 1115, 1116, 1118, 1119, 1120, 1121, 1123, 1124,
+ 1125, 1126, 1127, 1129, 1130, 1131, 1133, 1140, 1145, 1146,
+ 1147, 1151, 1159, 1163, 1164, 1165, 1166, 1167, 1168, 1175,
+ 1177, 1205, 1206, 1235, 1237, 1238, 1248, 1251, 1254, 1255,
+ 1295, 1296, 1298, 1300, 1301, 1303, 1304, 1312, 1317, 1325,
+ 1339, 1340, 1363, 1374, 1379, 1383, 1388, 1392, 1408, 1409,
+ 1410, 1413, 1422, 1426, 1427, 1428, 1429, 1433, 1436, 1448,
+ 1450, 1451, 1744, 1774, 1781, 1814, 2262, 2268, 2286, 2392,
+ 2456, 2502, 2510, 2518, 2528, 2569, 3171, 3195, 3262, 3286,
+ 3310, 3338, 3442, 3955, 4386, 4759, 5935, 6034, 6062, 6167,
+ 6547, 6573, 6720, 6721, 6725, 6944, 6984, 7105, 7207, 7557,
+ 7636, 7786, 7874, 7958, 7960, 7967, 15449, 15685, 15697, 16453,
+ 19683, 21124, 21839, 23150, 23307, 23876, 24317, 25017, 25126, 25450,
+ 25474, 27271, 30604, 32289,
+ };
+
+ auto original = LoadOriginal();
+ ASSERT_OK(original) << original.status().c_message();
+
+ auto actual = GetAllPids(*original);
+
+ for (auto pid : expected) {
+ ASSERT_TRUE(actual.count(pid))
+ << "pid " << pid << " was not found in the trace";
+ }
+
+ for (auto pid : actual) {
+ ASSERT_TRUE(expected.count(pid))
+ << "pid " << pid << " was found in the trace";
+ }
+}
+
+// Package name: "com.Unity.com.unity.multiplayer.samples.coop"
+// Package pid: 7105
+TEST_F(ScrubProcessStatsTest, OnlyKeepsStatsForPackage) {
+ auto result = Redact();
+ ASSERT_OK(result) << result.c_message();
+
+ auto redacted = LoadRedacted();
+ ASSERT_OK(redacted) << redacted.status().c_message();
+
+ auto actual = GetAllPids(*redacted);
+ ASSERT_EQ(actual.size(), 1u);
+ ASSERT_TRUE(actual.count(7105));
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 3778987..e472d06 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -4637,6 +4637,111 @@
kUnsetFtraceId,
420,
kUnsetSize},
+ {"f2fs_background_gc",
+ "f2fs",
+ {
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "dev", 1, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "wait_ms", 2, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "prefree", 3, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "free", 4, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ },
+ kUnsetFtraceId,
+ 495,
+ kUnsetSize},
+ {"f2fs_gc_begin",
+ "f2fs",
+ {
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "dev", 1, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "sync", 2, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "background", 3, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "dirty_nodes", 4, ProtoSchemaType::kInt64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "dirty_dents", 5, ProtoSchemaType::kInt64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "dirty_imeta", 6, ProtoSchemaType::kInt64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "free_sec", 7, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "free_seg", 8, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "reserved_seg", 9, ProtoSchemaType::kInt32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "prefree_seg", 10, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "gc_type", 11, ProtoSchemaType::kInt32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "no_bg_gc", 12, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "nr_free_secs", 13, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ },
+ kUnsetFtraceId,
+ 496,
+ kUnsetSize},
+ {"f2fs_gc_end",
+ "f2fs",
+ {
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "dev", 1, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "ret", 2, ProtoSchemaType::kInt32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "seg_freed", 3, ProtoSchemaType::kInt32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "sec_freed", 4, ProtoSchemaType::kInt32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "dirty_nodes", 5, ProtoSchemaType::kInt64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "dirty_dents", 6, ProtoSchemaType::kInt64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "dirty_imeta", 7, ProtoSchemaType::kInt64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "free_sec", 8, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "free_seg", 9, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "reserved_seg", 10, ProtoSchemaType::kInt32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "prefree_seg", 11, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ },
+ kUnsetFtraceId,
+ 497,
+ kUnsetSize},
{"fastrpc_dma_stat",
"fastrpc",
{
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_background_gc/format b/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_background_gc/format
new file mode 100644
index 0000000..f6aa901
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_background_gc/format
@@ -0,0 +1,14 @@
+name: f2fs_background_gc
+ID: 519
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:dev_t dev; offset:8; size:4; signed:0;
+ field:unsigned int wait_ms; offset:12; size:4; signed:0;
+ field:unsigned int prefree; offset:16; size:4; signed:0;
+ field:unsigned int free; offset:20; size:4; signed:0;
+
+print fmt: "dev = (%d,%d), wait_ms = %u, prefree = %u, free = %u", ((unsigned int) ((REC->dev) >> 20)), ((unsigned int) ((REC->dev) & ((1U << 20) - 1))), REC->wait_ms, REC->prefree, REC->free
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_gc_begin/format b/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_gc_begin/format
new file mode 100644
index 0000000..502deca
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_gc_begin/format
@@ -0,0 +1,21 @@
+name: f2fs_gc_begin
+ID: 520
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:dev_t dev; offset:8; size:4; signed:0;
+ field:int gc_type; offset:12; size:4; signed:1;
+ field:bool no_bg_gc; offset:16; size:1; signed:0;
+ field:unsigned int nr_free_secs; offset:20; size:4; signed:0;
+ field:long long dirty_nodes; offset:24; size:8; signed:1;
+ field:long long dirty_dents; offset:32; size:8; signed:1;
+ field:long long dirty_imeta; offset:40; size:8; signed:1;
+ field:unsigned int free_sec; offset:48; size:4; signed:0;
+ field:unsigned int free_seg; offset:52; size:4; signed:0;
+ field:int reserved_seg; offset:56; size:4; signed:1;
+ field:unsigned int prefree_seg; offset:60; size:4; signed:0;
+
+print fmt: "dev = (%d,%d), gc_type = %s, no_background_GC = %d, nr_free_secs = %u, nodes = %lld, dents = %lld, imeta = %lld, free_sec:%u, free_seg:%u, rsv_seg:%d, prefree_seg:%u", ((unsigned int) ((REC->dev) >> 20)), ((unsigned int) ((REC->dev) & ((1U << 20) - 1))), __print_symbolic(REC->gc_type, { 1, "Foreground GC" }, { 0, "Background GC" }), (REC->gc_type == 0) ? REC->no_bg_gc : -1, REC->nr_free_secs, REC->dirty_nodes, REC->dirty_dents, REC->dirty_imeta, REC->free_sec, REC->free_seg, REC->reserved_seg, REC->prefree_seg
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_gc_end/format b/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_gc_end/format
new file mode 100644
index 0000000..14870b3
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_gc_end/format
@@ -0,0 +1,21 @@
+name: f2fs_gc_end
+ID: 521
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:dev_t dev; offset:8; size:4; signed:0;
+ field:int ret; offset:12; size:4; signed:1;
+ field:int seg_freed; offset:16; size:4; signed:1;
+ field:int sec_freed; offset:20; size:4; signed:1;
+ field:long long dirty_nodes; offset:24; size:8; signed:1;
+ field:long long dirty_dents; offset:32; size:8; signed:1;
+ field:long long dirty_imeta; offset:40; size:8; signed:1;
+ field:unsigned int free_sec; offset:48; size:4; signed:0;
+ field:unsigned int free_seg; offset:52; size:4; signed:0;
+ field:int reserved_seg; offset:56; size:4; signed:1;
+ field:unsigned int prefree_seg; offset:60; size:4; signed:0;
+
+print fmt: "dev = (%d,%d), ret = %d, seg_freed = %d, sec_freed = %d, nodes = %lld, dents = %lld, imeta = %lld, free_sec:%u, free_seg:%u, rsv_seg:%d, prefree_seg:%u", ((unsigned int) ((REC->dev) >> 20)), ((unsigned int) ((REC->dev) & ((1U << 20) - 1))), REC->ret, REC->seg_freed, REC->sec_freed, REC->dirty_nodes, REC->dirty_dents, REC->dirty_imeta, REC->free_sec, REC->free_seg, REC->reserved_seg, REC->prefree_seg
diff --git a/test/data/android_cpu_eos.pb.sha256 b/test/data/android_cpu_eos.pb.sha256
new file mode 100644
index 0000000..15f2572
--- /dev/null
+++ b/test/data/android_cpu_eos.pb.sha256
@@ -0,0 +1 @@
+68377df0bb4fc55a26e2da4285aa441b6d1d3c4e99a192a7bc80876834954c2b
\ No newline at end of file
diff --git a/test/data/wattson_dsu_pmu.pb.sha256 b/test/data/wattson_dsu_pmu.pb.sha256
new file mode 100644
index 0000000..e547cee
--- /dev/null
+++ b/test/data/wattson_dsu_pmu.pb.sha256
@@ -0,0 +1 @@
+848d01bf930bd5b05a9d13c921a38b0101f6f8ec0b69e74ea2a6f408e118b995
\ No newline at end of file
diff --git a/test/data/wattson_eos_suspend.pb.sha256 b/test/data/wattson_eos_suspend.pb.sha256
new file mode 100644
index 0000000..52b8eef
--- /dev/null
+++ b/test/data/wattson_eos_suspend.pb.sha256
@@ -0,0 +1 @@
+dcae89e16d14152cd2cd19bcd7c800981041be74d002b3ab99dc469618d84401
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 1edf16f..1f711b8 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -97,6 +97,7 @@
from diff_tests.stdlib.common.tests import StdlibCommon
from diff_tests.stdlib.common.tests import StdlibCommon
from diff_tests.stdlib.counters.tests import StdlibCounterIntervals
+from diff_tests.stdlib.cpu.tests import CpuStdlib
from diff_tests.stdlib.dynamic_tables.tests import DynamicTables
from diff_tests.stdlib.graphs.dominator_tree_tests import DominatorTree
from diff_tests.stdlib.graphs.partition_tests import GraphPartitionTests
@@ -118,6 +119,7 @@
from diff_tests.stdlib.span_join.tests_smoke import SpanJoinSmoke
from diff_tests.stdlib.tests import StdlibSmoke
from diff_tests.stdlib.timestamps.tests import Timestamps
+from diff_tests.stdlib.wattson.tests import WattsonStdlib
from diff_tests.syntax.filtering_tests import PerfettoFiltering
from diff_tests.syntax.function_tests import PerfettoFunction
from diff_tests.syntax.include_tests import PerfettoInclude
@@ -243,6 +245,7 @@
stdlib_tests = [
*AndroidStdlib(index_path, 'stdlib/android', 'AndroidStdlib').fetch(),
+ *CpuStdlib(index_path, 'stdlib/cpu', 'CpuStdlib').fetch(),
*DominatorTree(index_path, 'stdlib/graphs', 'DominatorTree').fetch(),
*Frames(index_path, 'stdlib/android', 'Frames').fetch(),
*GraphSearchTests(index_path, 'stdlib/graphs',
@@ -280,6 +283,7 @@
*IntervalsIntersect(index_path, 'stdlib/intervals',
'StdlibIntervalsIntersect').fetch(),
*Timestamps(index_path, 'stdlib/timestamps', 'Timestamps').fetch(),
+ *WattsonStdlib(index_path, 'stdlib/wattson', 'WattsonStdlib').fetch(),
] + chrome_stdlib_tests
syntax_tests = [
diff --git a/test/trace_processor/diff_tests/parser/power/tests_power_rails.py b/test/trace_processor/diff_tests/parser/power/tests_power_rails.py
index 5085cd2..d08bbbc 100644
--- a/test/trace_processor/diff_tests/parser/power/tests_power_rails.py
+++ b/test/trace_processor/diff_tests/parser/power/tests_power_rails.py
@@ -21,6 +21,23 @@
class PowerPowerRails(TestSuite):
+ def test_android_power_rails_counters(self):
+ return DiffTestBlueprint(
+ trace=DataPath('power_rails.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE android.power_rails;
+ SELECT
+ power_rail_name, AVG(value), COUNT(*)
+ FROM android_power_rails_counters
+ GROUP BY 1
+ LIMIT 20;
+ """,
+ out=Csv("""
+ "power_rail_name","AVG(value)","COUNT(*)"
+ "power.PPVAR_VPH_PWR_ABH_uws",7390700.360656,61
+ "power.PPVAR_VPH_PWR_OLED_uws",202362991.655738,61
+ """))
+
def test_power_rails_power_rails(self):
return DiffTestBlueprint(
trace=DataPath('power_rails.pb'),
diff --git a/test/trace_processor/diff_tests/stdlib/cpu/tests.py b/test/trace_processor/diff_tests/stdlib/cpu/tests.py
new file mode 100644
index 0000000..0c32ed3
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/cpu/tests.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed 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 a
+#
+# 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.
+
+from python.generators.diff_tests.testing import Csv, Path, DataPath
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class CpuStdlib(TestSuite):
+ # Test CPU frequency counter grouping.
+ def test_cpu_eos_counters_freq(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_cpu_eos.pb'),
+ query=("""
+ INCLUDE PERFETTO MODULE cpu.freq;
+ select
+ track_id,
+ freq,
+ cpu,
+ sum(dur) as dur
+ from cpu_freq_counters
+ GROUP BY freq, cpu
+ """),
+ out=Csv("""
+ "track_id","freq","cpu","dur"
+ 33,614400,0,4755967239
+ 34,614400,1,4755971561
+ 35,614400,2,4755968228
+ 36,614400,3,4755964320
+ 33,864000,0,442371195
+ 34,864000,1,442397134
+ 35,864000,2,442417916
+ 36,864000,3,442434530
+ 33,1363200,0,897122398
+ 34,1363200,1,897144167
+ 35,1363200,2,897180154
+ 36,1363200,3,897216772
+ 33,1708800,0,2553979530
+ 34,1708800,1,2553923073
+ 35,1708800,2,2553866772
+ 36,1708800,3,2553814688
+ """))
+
+ # Test CPU idle state counter grouping.
+ def test_cpu_eos_counters_idle(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_cpu_eos.pb'),
+ query=("""
+ INCLUDE PERFETTO MODULE cpu.idle;
+ select
+ track_id,
+ idle,
+ cpu,
+ sum(dur) as dur
+ from cpu_idle_counters
+ GROUP BY idle, cpu
+ """),
+ out=Csv("""
+ "track_id","idle","cpu","dur"
+ 0,-1,0,2839828332
+ 37,-1,1,1977033843
+ 32,-1,2,1800498713
+ 1,-1,3,1884366297
+ 0,0,0,1833971336
+ 37,0,1,2285260950
+ 32,0,2,1348416182
+ 1,0,3,1338508968
+ 0,1,0,4013820433
+ 37,1,1,4386917600
+ 32,1,2,5532102915
+ 1,1,3,5462026920
+ """))
diff --git a/test/trace_processor/diff_tests/stdlib/wattson/tests.py b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
new file mode 100644
index 0000000..94c9bd7
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
@@ -0,0 +1,232 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed 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 a
+#
+# 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.
+
+from python.generators.diff_tests.testing import Csv, Path, DataPath
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class WattsonStdlib(TestSuite):
+ consolidate_tables_template = ('''
+ SELECT
+ sum(dur) as duration,
+ SUM(l3_hit_count) AS l3_hit_count,
+ SUM(l3_miss_count) AS l3_miss_count,
+ freq_0, idle_0, freq_1, idle_1, freq_2, idle_2, freq_3, idle_3,
+ freq_4, idle_4, freq_5, idle_5, freq_6, idle_6, freq_7, idle_7,
+ suspended
+ FROM SYSTEM_STATE_TABLE
+ GROUP BY
+ freq_0, idle_0, freq_1, idle_1, freq_2, idle_2, freq_3, idle_3,
+ freq_4, idle_4, freq_5, idle_5, freq_6, idle_6, freq_7, idle_7,
+ suspended
+ ORDER BY duration desc
+ LIMIT 20;
+ ''')
+
+ # Test fixup of deep idle offset and time marker window.
+ def test_wattson_time_window(self):
+ return DiffTestBlueprint(
+ trace=DataPath('wattson_dsu_pmu.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE wattson.system_state;
+
+ CREATE PERFETTO TABLE wattson_time_window
+ AS
+ SELECT 362426061658 AS ts, 5067704349 AS dur;
+
+ -- Final table that is cut off to fit within the requested time window.
+ CREATE VIRTUAL TABLE time_window_intersect
+ USING SPAN_JOIN(wattson_system_states, wattson_time_window);
+ """ + self.consolidate_tables_template.replace("SYSTEM_STATE_TABLE",
+ "time_window_intersect"),
+ out=Csv("""
+ "duration","l3_hit_count","l3_miss_count","freq_0","idle_0","freq_1","idle_1","freq_2","idle_2","freq_3","idle_3","freq_4","idle_4","freq_5","idle_5","freq_6","idle_6","freq_7","idle_7","suspended"
+ 58982760,2787779,1229222,574000,0,574000,1,574000,1,574000,0,553000,0,553000,0,500000,1,500000,0,0
+ 50664181,2364802,1133322,574000,0,574000,1,574000,1,574000,0,553000,1,553000,0,500000,0,500000,0,0
+ 41743544,2013295,917107,574000,0,574000,0,574000,1,574000,1,553000,0,553000,0,500000,0,500000,1,0
+ 33801135,1479851,684489,300000,0,300000,0,300000,1,300000,1,400000,0,400000,0,500000,0,500000,1,0
+ 32703489,1428203,690001,300000,0,300000,0,300000,1,300000,0,400000,0,400000,0,500000,0,500000,0,0
+ 29062133,1597555,719900,574000,0,574000,0,574000,1,574000,0,553000,1,553000,0,500000,1,500000,0,0
+ 28310872,1211262,566873,300000,0,300000,1,300000,1,300000,0,400000,0,400000,0,500000,0,500000,0,0
+ 26754474,1224826,569901,300000,0,300000,1,300000,0,300000,0,400000,1,400000,0,500000,0,500000,0,0
+ 25107621,1059311,473161,300000,0,300000,1,300000,1,300000,0,400000,0,400000,0,500000,1,500000,0,0
+ 23771603,987803,450930,300000,0,300000,1,300000,1,300000,0,400000,1,400000,0,500000,0,500000,0,0
+ 23721891,963220,409196,300000,0,300000,0,300000,1,300000,0,400000,1,400000,0,500000,1,500000,0,0
+ 22988523,984240,473025,300000,0,300000,0,300000,1,300000,0,400000,0,400000,0,500000,0,500000,1,0
+ 21790436,987511,449348,300000,0,300000,1,300000,1,300000,0,400000,0,400000,0,500000,0,500000,1,0
+ 21673975,1034856,445803,574000,0,574000,0,574000,1,574000,0,553000,0,553000,0,500000,0,500000,1,0
+ 20665650,974100,442861,300000,0,300000,0,300000,1,300000,0,400000,0,400000,1,500000,0,500000,0,0
+ 18024891,823424,339250,300000,0,300000,1,300000,0,300000,0,400000,0,400000,0,500000,1,500000,0,0
+ 17669272,826030,346995,574000,0,574000,0,574000,0,574000,0,553000,1,553000,0,500000,1,500000,0,0
+ 16774291,762738,348469,574000,0,574000,1,574000,1,574000,0,553000,0,553000,0,500000,0,500000,1,0
+ 16191449,689792,316923,300000,0,300000,0,300000,1,300000,0,400000,1,400000,0,500000,0,500000,0,0
+ 15918895,742531,325426,574000,0,574000,1,574000,0,574000,1,553000,0,553000,0,500000,1,500000,0,0
+ """))
+
+ # Test on Raven for checking system states and the DSU PMU counts.
+ def test_wattson_dsu_pmu(self):
+ return DiffTestBlueprint(
+ trace=DataPath('wattson_dsu_pmu.pb'),
+ query=("INCLUDE PERFETTO MODULE wattson.system_state;\n" +
+ self.consolidate_tables_template.replace(
+ "SYSTEM_STATE_TABLE", "wattson_system_states")),
+ out=Csv("""
+ "duration","l3_hit_count","l3_miss_count","freq_0","idle_0","freq_1","idle_1","freq_2","idle_2","freq_3","idle_3","freq_4","idle_4","freq_5","idle_5","freq_6","idle_6","freq_7","idle_7","suspended"
+ 1279130692,1318302,419159,300000,1,300000,1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+ 165854009,118369,42037,300000,-1,300000,1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+ 121156343,5846554,2343180,574000,0,574000,1,574000,0,574000,0,553000,0,553000,0,500000,1,500000,1,0
+ 72876331,133532,57759,300000,1,300000,1,300000,-1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+ 71060865,69029,22056,300000,1,300000,-1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+ 64501262,276098,309757,300000,1,300000,1,300000,1,300000,1,400000,1,400000,1,500000,-1,500000,1,0
+ 58982760,2787779,1229222,574000,0,574000,1,574000,1,574000,0,553000,0,553000,0,500000,1,500000,0,0
+ 51127388,50724,18075,300000,1,300000,1,300000,1,300000,-1,400000,1,400000,1,500000,1,500000,1,0
+ 50664181,2364802,1133322,574000,0,574000,1,574000,1,574000,0,553000,1,553000,0,500000,0,500000,0,0
+ 49948122,2216740,934893,300000,0,300000,1,300000,0,300000,0,400000,0,400000,0,500000,1,500000,1,0
+ 41743544,2013295,917107,574000,0,574000,0,574000,1,574000,1,553000,0,553000,0,500000,0,500000,1,0
+ 40606558,"[NULL]","[NULL]",1401000,1,1401000,1,1401000,1,1401000,1,400000,1,400000,1,2802000,1,2802000,1,0
+ 39887541,14272,1252,300000,0,300000,1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+ 38159789,1428203,690001,300000,0,300000,0,300000,1,300000,0,400000,0,400000,0,500000,0,500000,0,0
+ 33801135,1479851,684489,300000,0,300000,0,300000,1,300000,1,400000,0,400000,0,500000,0,500000,1,0
+ 31543574,34702,18036,300000,1,300000,1,300000,1,300000,1,400000,-1,400000,1,500000,1,500000,1,0
+ 31470669,163778,200331,1098000,1,1098000,1,1098000,1,1098000,1,400000,1,400000,1,500000,1,500000,-1,0
+ 30993650,39579,48376,300000,1,300000,1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,-1,0
+ 30144287,1396131,585581,574000,0,574000,1,574000,0,574000,0,553000,0,553000,0,500000,1,500000,0,0
+ 30009881,"[NULL]","[NULL]",1328000,1,1328000,1,1328000,1,1328000,1,2253000,1,2253000,1,500000,1,500000,1,0
+ """))
+
+ # Test on eos to check that suspend states are being calculated appropriately.
+ def test_wattson_suspend(self):
+ return DiffTestBlueprint(
+ trace=DataPath('wattson_eos_suspend.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE wattson.system_state;
+ SELECT
+ sum(dur) as duration,
+ freq_0, idle_0, freq_1, idle_1, freq_2, idle_2, freq_3, idle_3,
+ suspended
+ FROM wattson_system_states
+ GROUP BY
+ freq_0, idle_0, freq_1, idle_1, freq_2, idle_2, freq_3, idle_3,
+ suspended
+ ORDER BY duration desc
+ LIMIT 20;
+ """,
+ out=Csv("""
+ "duration","freq_0","idle_0","freq_1","idle_1","freq_2","idle_2","freq_3","idle_3","suspended"
+ 16606175990,614400,1,614400,1,614400,1,614400,1,0
+ 10648392546,1708800,-1,1708800,-1,1708800,-1,1708800,-1,1
+ 6933558399,1708800,-1,1708800,-1,1708800,-1,1708800,-1,0
+ 1649400745,614400,0,614400,0,614400,0,614400,0,0
+ 1199187488,614400,-1,614400,1,614400,1,614400,1,0
+ 945900007,1708800,0,1708800,0,1708800,0,1708800,0,0
+ 936351409,1363200,0,1363200,0,1363200,0,1363200,1,0
+ 708490325,1708800,0,1708800,0,1708800,0,1708800,1,0
+ 706695995,1708800,1,1708800,1,1708800,1,1708800,1,0
+ 656873956,1363200,1,1363200,1,1363200,1,1363200,1,0
+ 633440914,1363200,0,1363200,0,1363200,0,1363200,0,0
+ 627708654,1708800,-1,1708800,0,1708800,0,1708800,0,0
+ 620315547,1708800,-1,1708800,1,1708800,1,1708800,1,0
+ 578173274,1708800,-1,1708800,0,1708800,0,1708800,-1,0
+ 530967964,1708800,-1,1708800,-1,1708800,0,1708800,-1,0
+ 516281990,1708800,0,1708800,0,1708800,0,1708800,-1,0
+ 473910837,1363200,-1,1363200,0,1363200,0,1363200,1,0
+ 461831724,1708800,0,1708800,-1,1708800,0,1708800,0,0
+ 402233299,1708800,-1,1708800,-1,1708800,-1,1708800,1,0
+ 375051979,864000,1,864000,1,864000,1,864000,1,0
+ """))
+
+ # Test cpu_idle.sql module to make sure final CPU idle system state is proper.
+ def test_wattson_cpu_idle(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_cpu_eos.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE wattson.cpu_idle;
+ SELECT ts, dur, idle_0, idle_1, idle_2, idle_3
+ FROM _cpu_idle_all
+ ORDER BY ts DESC
+ LIMIT 20;
+ """,
+ out=Csv("""
+ "ts","dur","idle_0","idle_1","idle_2","idle_3"
+ 183589213730,322865,-1,-1,-1,-1
+ 183589083105,130625,-1,0,-1,-1
+ 183587834095,1249010,-1,-1,-1,-1
+ 183586743417,1090678,0,-1,-1,-1
+ 183584248209,2495208,0,-1,-1,0
+ 183584002220,245989,-1,-1,-1,0
+ 183580262740,3739480,0,-1,-1,0
+ 183580247324,15416,0,-1,-1,-1
+ 183580135813,111511,-1,-1,-1,-1
+ 183580002792,133021,-1,-1,-1,0
+ 183576199459,3803333,0,-1,-1,0
+ 183576002376,197083,-1,-1,-1,0
+ 183575781386,220990,0,-1,-1,0
+ 183575764355,17031,0,-1,-1,-1
+ 183575719824,44531,-1,-1,-1,-1
+ 183575706490,13334,-1,-1,-1,0
+ 183575630865,75625,-1,-1,-1,-1
+ 183575503886,126979,0,-1,-1,-1
+ 183573856542,1647344,0,-1,-1,0
+ 183573750032,106510,-1,-1,-1,0
+ """))
+
+ # Test cpu_freq.sql module to make sure final CPU frequency states are proper.
+ def test_wattson_cpu_freq(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_cpu_eos.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE wattson.cpu_freq;
+ SELECT ts, dur, freq_0, freq_1, freq_2, freq_3
+ FROM _cpu_freq_all
+ ORDER BY ts DESC
+ LIMIT 20;
+ """,
+ out=Csv("""
+ "ts","dur","freq_0","freq_1","freq_2","freq_3"
+ 183219975813,369560782,1708800,1708800,1708800,1708800
+ 183219971595,4218,1708800,1708800,1708800,1363200
+ 183219967324,4271,1708800,1708800,1363200,1363200
+ 183219961334,5990,1708800,1363200,1363200,1363200
+ 183217603313,2358021,1363200,1363200,1363200,1363200
+ 183217599147,4166,1363200,1363200,1363200,614400
+ 183217594720,4427,1363200,1363200,614400,614400
+ 183217584459,10261,1363200,614400,614400,614400
+ 183099692324,117892135,614400,614400,614400,614400
+ 183099688001,4323,614400,614400,614400,1708800
+ 183099683418,4583,614400,614400,1708800,1708800
+ 183099677584,5834,614400,1708800,1708800,1708800
+ 183000228105,99449479,1708800,1708800,1708800,1708800
+ 183000223938,4167,1708800,1708800,1708800,864000
+ 183000219303,4635,1708800,1708800,864000,864000
+ 183000213313,5990,1708800,864000,864000,864000
+ 182984137428,16075885,864000,864000,864000,864000
+ 182984133522,3906,864000,864000,864000,1363200
+ 182984129355,4167,864000,864000,1363200,1363200
+ 182984123678,5677,864000,1363200,1363200,1363200
+ """))
+
+ # Test that the device name can be extracted from the trace's metadata.
+ def test_wattson_device_name(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_cpu_eos.pb'),
+ query=("""
+ INCLUDE PERFETTO MODULE android.device;
+ select name from android_device_name
+ """),
+ out=Csv("""
+ "name"
+ "eos"
+ """))
diff --git a/tools/gen_bazel b/tools/gen_bazel
index 049cf40..6036809 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -284,6 +284,7 @@
gn = gn_utils.GnParser(gn_desc)
project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
tool_name = os.path.relpath(os.path.abspath(__file__), project_root)
+ tool_name = tool_name.replace('\\', '/')
res = '''
# Copyright (C) 2022 The Android Open Source Project
#
@@ -746,6 +747,7 @@
gn = gn_utils.GnParser(gn_desc)
project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
tool_name = os.path.relpath(os.path.abspath(__file__), project_root)
+ tool_name = tool_name.replace('\\', '/')
res = '''
# Copyright (C) 2019 The Android Open Source Project
#
@@ -915,14 +917,14 @@
contents = generate_build(desc, args.targets or default_targets, extras)
out_files.append(args.output + '.swp')
- with open(out_files[-1], 'w') as out_f:
+ with open(out_files[-1], 'w', newline='\n') as out_f:
out_f.write(contents)
# Generate the python BUILD file.
python_gen = PythonBuildGenerator()
python_contents = python_gen.generate(desc)
out_files.append(args.output_python + '.swp')
- with open(out_files[-1], 'w') as out_f:
+ with open(out_files[-1], 'w', newline='\n') as out_f:
out_f.write(python_contents)
# Generate the build flags file.
diff --git a/tools/gn_utils.py b/tools/gn_utils.py
index 65c0ff0..d0417d7 100644
--- a/tools/gn_utils.py
+++ b/tools/gn_utils.py
@@ -123,7 +123,7 @@
source files in the amalgamated result.
"""
targets = [t.replace('//', '') for t in targets]
- with open(os.devnull, 'w') as devnull:
+ with open(os.devnull, 'w', newline='\n') as devnull:
stdout = devnull if quiet else None
cmd = _tool_path('ninja', system_buildtools) + targets
subprocess.check_call(cmd, cwd=os.path.abspath(out), stdout=stdout)
@@ -211,7 +211,7 @@
res = 1
os.unlink(tmp_file)
else:
- os.rename(tmp_file, target_file)
+ os.replace(tmp_file, target_file)
return res
diff --git a/ui/PRESUBMIT.py b/ui/PRESUBMIT.py
index 068f9de..be0ad24 100644
--- a/ui/PRESUBMIT.py
+++ b/ui/PRESUBMIT.py
@@ -99,7 +99,7 @@
def file_filter(x):
return input_api.FilterSourceFile(
- x, files_to_check=[r'.*\.ts$', r'.*\.js$'])
+ x, files_to_check=[r'.*\.ts$', r'.*\.js$', r'.*\.scss$'])
files = input_api.AffectedSourceFiles(file_filter)
diff --git a/ui/src/assets/details.scss b/ui/src/assets/details.scss
index f3e529a..d09690a 100644
--- a/ui/src/assets/details.scss
+++ b/ui/src/assets/details.scss
@@ -173,43 +173,6 @@
width: 50%;
}
}
- &.flamegraph-profile {
- display: flex;
- justify-content: space-between;
- align-content: center;
- height: 30px;
- padding: 0;
- font-size: 12px;
- * {
- align-self: center;
- }
- .options {
- display: inline-flex;
- justify-content: space-around;
- }
- .details {
- display: inline-flex;
- justify-content: flex-end;
- }
- .title {
- justify-self: start;
- margin-left: 5px;
- font-size: 14px;
- margin-right: 10px;
- }
- .time {
- justify-self: end;
- margin-right: 10px;
- }
- .selected {
- justify-self: end;
- margin-right: 10px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- width: 200px;
- }
- }
}
table {
@@ -709,3 +672,35 @@
.pf-noselection {
height: 100%;
}
+
+.flamegraph-profile {
+ height: 100%;
+ // This is required to position locally-scoped (i.e. non-full-screen) modal
+ // dialogs within the panel, as they use position: absolute.
+ position: relative;
+
+ .time {
+ justify-self: end;
+ margin-right: 10px;
+ }
+ .selected {
+ justify-self: end;
+ margin-right: 10px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 200px;
+ }
+ .flamegraph-content {
+ overflow: auto;
+ height: 100%;
+
+ .loading-container {
+ font-size: larger;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ }
+ }
+}
diff --git a/ui/src/assets/record.scss b/ui/src/assets/record.scss
index 7226e88..7f8e16a 100644
--- a/ui/src/assets/record.scss
+++ b/ui/src/assets/record.scss
@@ -689,6 +689,10 @@
max-height: 0;
}
+ .extended-desc {
+ grid-column: span 2;
+ }
+
&.enabled {
.probe-config {
opacity: 1;
diff --git a/ui/src/common/recordingV2/recording_config_utils.ts b/ui/src/common/recordingV2/recording_config_utils.ts
index dff19c2..334ad25 100644
--- a/ui/src/common/recordingV2/recording_config_utils.ts
+++ b/ui/src/common/recordingV2/recording_config_utils.ts
@@ -23,6 +23,7 @@
BufferConfig,
ChromeConfig,
DataSourceConfig,
+ EtwConfig,
FtraceConfig,
HeapprofdConfig,
JavaContinuousDumpConfig,
@@ -649,31 +650,35 @@
}
// Keep these last. The stages above can enrich them.
+ if (
+ targetInfo.targetType !== 'WINDOWS' &&
+ targetInfo.targetType !== 'CHROME'
+ ) {
+ if (sysStatsCfg !== undefined) {
+ const ds = new TraceConfig.DataSource();
+ ds.config = new DataSourceConfig();
+ ds.config.name = 'linux.sys_stats';
+ ds.config.sysStatsConfig = sysStatsCfg;
+ protoCfg.dataSources.push(ds);
+ }
- if (sysStatsCfg !== undefined && targetInfo.targetType !== 'CHROME') {
- const ds = new TraceConfig.DataSource();
- ds.config = new DataSourceConfig();
- ds.config.name = 'linux.sys_stats';
- ds.config.sysStatsConfig = sysStatsCfg;
- protoCfg.dataSources.push(ds);
- }
+ if (heapprofd !== undefined) {
+ const ds = new TraceConfig.DataSource();
+ ds.config = new DataSourceConfig();
+ ds.config.targetBuffer = 0;
+ ds.config.name = 'android.heapprofd';
+ ds.config.heapprofdConfig = heapprofd;
+ protoCfg.dataSources.push(ds);
+ }
- if (heapprofd !== undefined && targetInfo.targetType !== 'CHROME') {
- const ds = new TraceConfig.DataSource();
- ds.config = new DataSourceConfig();
- ds.config.targetBuffer = 0;
- ds.config.name = 'android.heapprofd';
- ds.config.heapprofdConfig = heapprofd;
- protoCfg.dataSources.push(ds);
- }
-
- if (javaHprof !== undefined && targetInfo.targetType !== 'CHROME') {
- const ds = new TraceConfig.DataSource();
- ds.config = new DataSourceConfig();
- ds.config.targetBuffer = 0;
- ds.config.name = 'android.java_hprof';
- ds.config.javaHprofConfig = javaHprof;
- protoCfg.dataSources.push(ds);
+ if (javaHprof !== undefined) {
+ const ds = new TraceConfig.DataSource();
+ ds.config = new DataSourceConfig();
+ ds.config.targetBuffer = 0;
+ ds.config.name = 'android.java_hprof';
+ ds.config.javaHprofConfig = javaHprof;
+ protoCfg.dataSources.push(ds);
+ }
}
if (
@@ -757,6 +762,28 @@
}
}
+ if (
+ targetInfo.targetType === 'WINDOWS' ||
+ uiCfg.etwCSwitch ||
+ uiCfg.etwThreadState
+ ) {
+ const ds = new TraceConfig.DataSource();
+ ds.config = new DataSourceConfig();
+ ds.config.name = 'windows.etw';
+ ds.config.etwConfig = new EtwConfig();
+
+ const kernelFlags: EtwConfig.KernelFlag[] = [];
+
+ if (uiCfg.etwCSwitch) {
+ kernelFlags.push(EtwConfig.KernelFlag.CSWITCH);
+ }
+ if (uiCfg.etwThreadState) {
+ kernelFlags.push(EtwConfig.KernelFlag.DISPATCHER);
+ }
+ ds.config.etwConfig.kernelFlags = kernelFlags;
+ protoCfg.dataSources.push(ds);
+ }
+
return protoCfg;
}
diff --git a/ui/src/common/recordingV2/recording_interfaces_v2.ts b/ui/src/common/recordingV2/recording_interfaces_v2.ts
index ed5fdbf..805def8 100644
--- a/ui/src/common/recordingV2/recording_interfaces_v2.ts
+++ b/ui/src/common/recordingV2/recording_interfaces_v2.ts
@@ -79,7 +79,7 @@
}
export interface HostOsTargetInfo extends TargetInfoBase {
- targetType: 'LINUX' | 'MACOS';
+ targetType: 'LINUX' | 'MACOS' | 'WINDOWS';
}
// Holds information about a target. It's used by the UI and the logic which
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 92efee5..43f205e 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -580,7 +580,16 @@
| 'LONG_TRACE';
// 'Q','P','O' for Android, 'L' for Linux, 'C' for Chrome.
-export declare type TargetOs = 'S' | 'R' | 'Q' | 'P' | 'O' | 'C' | 'L' | 'CrOS';
+export declare type TargetOs =
+ | 'S'
+ | 'R'
+ | 'Q'
+ | 'P'
+ | 'O'
+ | 'C'
+ | 'L'
+ | 'CrOS'
+ | 'Win';
export function isAndroidP(target: RecordingTarget) {
return target.os === 'P';
@@ -602,6 +611,10 @@
return target.os === 'L';
}
+export function isWindowsTarget(target: RecordingTarget) {
+ return target.os === 'Win';
+}
+
export function isAdbTarget(
target: RecordingTarget,
): target is AdbRecordingTarget {
@@ -638,6 +651,7 @@
{os: 'C', name: 'Chrome'},
{os: 'CrOS', name: 'Chrome OS (system trace)'},
{os: 'L', name: 'Linux desktop'},
+ {os: 'Win', name: 'Windows desktop'},
];
}
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index a10252c..b1c00a4 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -300,6 +300,7 @@
await this.args.engine.query(`select value from stats
where severity = 'error' and name = 'heap_graph_non_finalized_graph'`)
).firstRow({value: NUM}).value > 0;
+ flamegraphDetails.graphLoading = false;
publishFlamegraphDetails(flamegraphDetails);
}
@@ -317,8 +318,10 @@
if (this.flamegraphDatasets.has(key)) {
currentData = this.flamegraphDatasets.get(key)!;
} else {
- // TODO(b/330703412): Show loading state.
-
+ publishFlamegraphDetails({
+ ...globals.flamegraphDetails,
+ graphLoading: true,
+ });
// Collecting data for drawing flamegraph for selected profile.
// Data needs to be in following format:
// id, name, parent_id, depth, total_size
diff --git a/ui/src/controller/record_config_types.ts b/ui/src/controller/record_config_types.ts
index 61e05e2..beb52d9 100644
--- a/ui/src/controller/record_config_types.ts
+++ b/ui/src/controller/record_config_types.ts
@@ -107,6 +107,9 @@
audio: bool(),
video: bool(),
+ etwCSwitch: bool(),
+ etwThreadState: bool(),
+
symbolizeKsyms: bool(),
// Enabling stack sampling
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index 2ce0a0b..1398581 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -64,7 +64,7 @@
uiCfg: RecordConfig,
target: RecordingTarget,
): TraceConfig {
- let targetType: 'ANDROID' | 'CHROME' | 'CHROME_OS' | 'LINUX';
+ let targetType: 'ANDROID' | 'CHROME' | 'CHROME_OS' | 'LINUX' | 'WINDOWS';
let androidApiLevel!: number;
switch (target.os) {
case 'L':
@@ -76,6 +76,9 @@
case 'CrOS':
targetType = 'CHROME_OS';
break;
+ case 'Win':
+ targetType = 'WINDOWS';
+ break;
case 'S':
androidApiLevel = 31;
targetType = 'ANDROID';
@@ -105,14 +108,12 @@
dataSources: [],
name: '',
};
- } else if (targetType === 'CHROME' || targetType === 'CHROME_OS') {
+ } else {
targetInfo = {
targetType,
dataSources: [],
name: '',
};
- } else {
- targetInfo = {targetType, dataSources: [], name: ''};
}
return genTraceConfig(uiCfg, targetInfo);
diff --git a/ui/src/frontend/flamegraph_panel.ts b/ui/src/frontend/flamegraph_panel.ts
index fea7fb2..0f4cba2 100644
--- a/ui/src/frontend/flamegraph_panel.ts
+++ b/ui/src/frontend/flamegraph_panel.ts
@@ -30,6 +30,8 @@
import {Icon} from '../widgets/icon';
import {Modal, ModalAttrs} from '../widgets/modal';
import {Popup} from '../widgets/popup';
+import {EmptyState} from '../widgets/empty_state';
+import {Spinner} from '../widgets/spinner';
import {Flamegraph, NodeRendering} from './flamegraph';
import {globals} from './globals';
@@ -37,7 +39,9 @@
import {Router} from './router';
import {getCurrentTrace} from './sidebar';
import {convertTraceToPprofAndDownload} from './trace_converter';
+import {ButtonBar} from '../widgets/button';
import {DurationWidget} from './widgets/duration';
+import {DetailsShell} from '../widgets/details_shell';
const HEADER_HEIGHT = 30;
@@ -90,33 +94,31 @@
? this.flamegraph.getHeight() + HEADER_HEIGHT
: 0;
return m(
- '.details-panel',
+ '.flamegraph-profile',
this.maybeShowModal(flamegraphDetails.graphIncomplete),
m(
- '.details-panel-heading.flamegraph-profile',
- {onclick: (e: MouseEvent) => e.stopPropagation()},
- [
- m('div.options', [
- m(
- 'div.title',
- this.getTitle(),
- this.profileType === ProfileType.MIXED_HEAP_PROFILE &&
+ DetailsShell,
+ {
+ fillParent: true,
+ title: m(
+ 'div.title',
+ this.getTitle(),
+ this.profileType === ProfileType.MIXED_HEAP_PROFILE &&
+ m(
+ Popup,
+ {
+ trigger: m(Icon, {icon: 'warning'}),
+ },
m(
- Popup,
- {
- trigger: m(Icon, {icon: 'warning'}),
- },
- m(
- '',
- {style: {width: '300px'}},
- 'This is a mixed java/native heap profile, free()s are not visualized. To visualize free()s, remove "all_heaps: true" from the config.',
- ),
+ '',
+ {style: {width: '300px'}},
+ 'This is a mixed java/native heap profile, free()s are not visualized. To visualize free()s, remove "all_heaps: true" from the config.',
),
- ':',
- ),
- this.getViewingOptionButtons(),
- ]),
- m('div.details', [
+ ),
+ ':',
+ ),
+ description: this.getViewingOptionButtons(),
+ buttons: [
m(
'div.selected',
`Selected function: ${toSelectedCallsite(
@@ -145,23 +147,39 @@
this.downloadPprof();
},
}),
- ]),
- ],
+ ],
+ },
+ m(
+ '.flamegraph-content',
+ flamegraphDetails.graphLoading
+ ? m(
+ '.loading-container',
+ m(
+ EmptyState,
+ {
+ icon: 'bar_chart',
+ title: 'Computing graph ...',
+ className: 'flamegraph-loading',
+ },
+ m(Spinner, {easing: true}),
+ ),
+ )
+ : m(`canvas[ref=canvas]`, {
+ style: `height:${height}px; width:100%`,
+ onmousemove: (e: MouseEvent) => {
+ const {offsetX, offsetY} = e;
+ this.onMouseMove({x: offsetX, y: offsetY});
+ },
+ onmouseout: () => {
+ this.onMouseOut();
+ },
+ onclick: (e: MouseEvent) => {
+ const {offsetX, offsetY} = e;
+ this.onMouseClick({x: offsetX, y: offsetY});
+ },
+ }),
+ ),
),
- m(`canvas[ref=canvas]`, {
- style: `height:${height}px; width:100%`,
- onmousemove: (e: MouseEvent) => {
- const {offsetX, offsetY} = e;
- this.onMouseMove({x: offsetX, y: offsetY});
- },
- onmouseout: () => {
- this.onMouseOut();
- },
- onclick: (e: MouseEvent) => {
- const {offsetX, offsetY} = e;
- this.onMouseClick({x: offsetX, y: offsetY});
- },
- }),
);
} else {
return m(
@@ -260,7 +278,7 @@
getViewingOptionButtons(): m.Children {
return m(
- 'div',
+ ButtonBar,
...FlamegraphDetailsPanel.selectViewingOptions(
assertExists(this.profileType),
),
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 1af1399..5cecd7e 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -160,6 +160,8 @@
// When heap_graph_non_finalized_graph has a count >0, we mark the graph
// as incomplete.
graphIncomplete?: boolean;
+ // About to show a new graph whose data is not ready yet.
+ graphLoading?: boolean;
}
export interface CpuProfileDetails {
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
index 631f814..ad59ea8 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/frontend/record_page.ts
@@ -25,6 +25,7 @@
isChromeTarget,
isCrOSTarget,
isLinuxTarget,
+ isWindowsTarget,
LoadedConfig,
MAX_TIME,
RecordingTarget,
@@ -55,6 +56,7 @@
import {PowerSettings} from './recording/power_settings';
import {RecordingSectionAttrs} from './recording/recording_sections';
import {RecordingSettings} from './recording/recording_settings';
+import {EtwSettings} from './recording/etw_settings';
export const PERSIST_CONFIG_FLAG = featureFlags.register({
id: 'persistConfigsUI',
@@ -68,6 +70,7 @@
'instructions',
'config',
'cpu',
+ 'etw',
'gpu',
'power',
'memory',
@@ -807,6 +810,15 @@
m('.sub', 'Lightweight stack polling'),
),
);
+ const etwProbe = m(
+ 'a[href="#!/record/etw"]',
+ m(
+ `li${routePage === 'etw' ? '.active' : ''}`,
+ m('i.material-icons', 'subtitles'),
+ m('.title', 'ETW Tracing Config'),
+ m('.sub', 'Context switch, Thread state'),
+ ),
+ );
const recInProgress = globals.state.recordingInProgress;
const probes = [];
@@ -814,6 +826,8 @@
probes.push(cpuProbe, powerProbe, memoryProbe, chromeProbe, advancedProbe);
} else if (isChromeTarget(target)) {
probes.push(chromeProbe);
+ } else if (isWindowsTarget(target)) {
+ probes.push(chromeProbe, etwProbe);
} else {
probes.push(
cpuProbe,
@@ -908,6 +922,7 @@
['chrome', ChromeSettings],
['tracePerf', LinuxPerfSettings],
['advanced', AdvancedSettings],
+ ['etw', EtwSettings],
]);
for (const [section, component] of settingsSections.entries()) {
pages.push(
diff --git a/ui/src/frontend/record_page_v2.ts b/ui/src/frontend/record_page_v2.ts
index 7e6e4f0..db87cfa 100644
--- a/ui/src/frontend/record_page_v2.ts
+++ b/ui/src/frontend/record_page_v2.ts
@@ -48,6 +48,7 @@
import {AndroidSettings} from './recording/android_settings';
import {ChromeSettings} from './recording/chrome_settings';
import {CpuSettings} from './recording/cpu_settings';
+import {EtwSettings} from './recording/etw_settings';
import {GpuSettings} from './recording/gpu_settings';
import {LinuxPerfSettings} from './recording/linux_perf_settings';
import {MemorySettings} from './recording/memory_settings';
@@ -488,6 +489,15 @@
m('.sub', 'Lightweight stack polling'),
),
);
+ const etwProbe = m(
+ 'a[href="#!/record/etw"]',
+ m(
+ `li${routePage === 'etw' ? '.active' : ''}`,
+ m('i.material-icons', 'subtitles'),
+ m('.title', 'ETW Tracing Config'),
+ m('.sub', 'Context switch, Thread state'),
+ ),
+ );
// We only display the probes when we have a valid target, so it's not
// possible for the target to be undefined here.
@@ -495,6 +505,8 @@
const probes = [];
if (targetType === 'CHROME_OS' || targetType === 'LINUX') {
probes.push(cpuProbe, powerProbe, memoryProbe, chromeProbe, advancedProbe);
+ } else if (targetType === 'WINDOWS') {
+ probes.push(chromeProbe, etwProbe);
} else if (targetType === 'CHROME') {
probes.push(chromeProbe);
} else {
@@ -618,6 +630,7 @@
['chrome', ChromeSettings],
['tracePerf', LinuxPerfSettings],
['advanced', AdvancedSettings],
+ ['etw', EtwSettings],
]);
for (const [section, component] of settingsSections.entries()) {
pages.push(
diff --git a/ui/src/frontend/record_widgets.ts b/ui/src/frontend/record_widgets.ts
index 3385655..fc9d2f4 100644
--- a/ui/src/frontend/record_widgets.ts
+++ b/ui/src/frontend/record_widgets.ts
@@ -91,7 +91,11 @@
),
attrs.compact
? ''
- : m('div', m('div', attrs.descr), m('.probe-config', children)),
+ : m(
+ `div${attrs.img ? '' : '.extended-desc'}`,
+ m('div', attrs.descr),
+ m('.probe-config', children),
+ ),
);
}
}
diff --git a/ui/src/frontend/recording/etw_settings.ts b/ui/src/frontend/recording/etw_settings.ts
new file mode 100644
index 0000000..64ffc51
--- /dev/null
+++ b/ui/src/frontend/recording/etw_settings.ts
@@ -0,0 +1,41 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed 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.
+
+import m from 'mithril';
+
+import {Probe, ProbeAttrs} from '../record_widgets';
+
+import {RecordingSectionAttrs} from './recording_sections';
+
+export class EtwSettings implements m.ClassComponent<RecordingSectionAttrs> {
+ view({attrs}: m.CVnode<RecordingSectionAttrs>) {
+ return m(
+ `.record-section${attrs.cssClass}`,
+ m(Probe, {
+ title: 'CSwitch',
+ img: null,
+ descr: `Enables to recording of context switches.`,
+ setEnabled: (cfg, val) => (cfg.etwCSwitch = val),
+ isEnabled: (cfg) => cfg.etwCSwitch,
+ } as ProbeAttrs),
+ m(Probe, {
+ title: 'Dispatcher',
+ img: null,
+ descr: 'Enables to get thread state.',
+ setEnabled: (cfg, val) => (cfg.etwThreadState = val),
+ isEnabled: (cfg) => cfg.etwThreadState,
+ } as ProbeAttrs),
+ );
+ }
+}
diff --git a/ui/src/protos/index.ts b/ui/src/protos/index.ts
index 77e8b5f..378fcfb 100644
--- a/ui/src/protos/index.ts
+++ b/ui/src/protos/index.ts
@@ -33,6 +33,7 @@
import EnableMetatraceArgs = protos.perfetto.protos.EnableMetatraceArgs;
import EnableTracingRequest = protos.perfetto.protos.EnableTracingRequest;
import EnableTracingResponse = protos.perfetto.protos.EnableTracingResponse;
+import EtwConfig = protos.perfetto.protos.EtwConfig;
import FreeBuffersRequest = protos.perfetto.protos.FreeBuffersRequest;
import FreeBuffersResponse = protos.perfetto.protos.FreeBuffersResponse;
import FtraceConfig = protos.perfetto.protos.FtraceConfig;
@@ -101,6 +102,7 @@
EnableMetatraceArgs,
EnableTracingRequest,
EnableTracingResponse,
+ EtwConfig,
FreeBuffersRequest,
FreeBuffersResponse,
FtraceConfig,