Merge "gn: fix failing security checks in binskip" into main
diff --git a/Android.bp b/Android.bp
index a356ed1..f58dc80 100644
--- a/Android.bp
+++ b/Android.bp
@@ -11796,6 +11796,7 @@
         "src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql",
         "src/trace_processor/perfetto_sql/stdlib/linux/cpu_idle.sql",
         "src/trace_processor/perfetto_sql/stdlib/pkvm/hypervisor.sql",
+        "src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql",
     ],
     cmd: "$(location tools/gen_amalgamated_sql.py) --namespace=stdlib --cpp-out=$(out) $(in)",
diff --git a/BUILD b/BUILD
index 91da2de..fae1e4b 100644
--- a/BUILD
+++ b/BUILD
@@ -2305,6 +2305,14 @@
     ],
 )
 
+# GN target: //src/trace_processor/perfetto_sql/stdlib/prelude:prelude
+perfetto_filegroup(
+    name = "src_trace_processor_perfetto_sql_stdlib_prelude_prelude",
+    srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql",
+    ],
+)
+
 # GN target: //src/trace_processor/perfetto_sql/stdlib/sched:sched
 perfetto_filegroup(
     name = "src_trace_processor_perfetto_sql_stdlib_sched_sched",
@@ -2325,6 +2333,7 @@
         ":src_trace_processor_perfetto_sql_stdlib_intervals_intervals",
         ":src_trace_processor_perfetto_sql_stdlib_linux_linux",
         ":src_trace_processor_perfetto_sql_stdlib_pkvm_pkvm",
+        ":src_trace_processor_perfetto_sql_stdlib_prelude_prelude",
         ":src_trace_processor_perfetto_sql_stdlib_sched_sched",
     ],
     outs = [
diff --git a/infra/perfetto.dev/src/gen_stdlib_docs_md.py b/infra/perfetto.dev/src/gen_stdlib_docs_md.py
index 3a61b37..b0ed5c7 100644
--- a/infra/perfetto.dev/src/gen_stdlib_docs_md.py
+++ b/infra/perfetto.dev/src/gen_stdlib_docs_md.py
@@ -47,6 +47,24 @@
     long_s = []
     long_s.append(f'## Module: {self.module_name}')
 
+    if self.module_name == 'prelude':
+      # Prelude is a special module which is automatically imported and doesn't
+      # have any include keys.
+      objs = '\n'.join(obj for file in self.files_md for obj in file.objs)
+      if objs:
+        long_s.append('#### Views/Tables')
+        long_s.append(objs)
+      funs = '\n'.join(fun for file in self.files_md for fun in file.funs)
+      if funs:
+        long_s.append('#### Functions')
+        long_s.append(funs)
+      table_funs = '\n'.join(
+          view_fun for file in self.files_md for view_fun in file.view_funs)
+      if table_funs:
+        long_s.append('#### Table Functions')
+        long_s.append(table_funs)
+      return '\n'.join(long_s)
+
     for file in self.files_md:
       if not file.objs and not file.funs and not file.view_funs:
         continue
@@ -70,6 +88,7 @@
 
   def __init__(self, module_name, file_dict):
     self.import_key = file_dict['import_key']
+    import_key_name = self.import_key if module_name != 'prelude' else 'N/A'
     self.objs, self.funs, self.view_funs = [], [], []
     summary_objs_list, summary_funs_list, summary_view_funs_list = [], [], []
 
@@ -81,7 +100,7 @@
       # Add summary of imported view/table
       desc = data['desc'].split('.')[0]
       summary_objs_list.append(f'''[{data['name']}](#{anchor})|'''
-                               f'''{file_dict['import_key']}|'''
+                               f'''{import_key_name}|'''
                                f'''{desc}''')
 
       self.objs.append(f'''\n\n<a name="{anchor}"></a>'''
@@ -102,7 +121,7 @@
 
       # Add summary of imported function
       summary_funs_list.append(f'''[{data['name']}](#{anchor})|'''
-                               f'''{file_dict['import_key']}|'''
+                               f'''{import_key_name}|'''
                                f'''{data['return_type']}|'''
                                f'''{data['desc'].split('.')[0]}''')
       self.funs.append(
@@ -125,7 +144,7 @@
       anchor = rf'''view_fun/{module_name}/{data['name']}'''
       # Add summary of imported view function
       summary_view_funs_list.append(f'''[{data['name']}](#{anchor})|'''
-                                    f'''{file_dict['import_key']}|'''
+                                    f'''{import_key_name}|'''
                                     f'''{data['desc'].split('.')[0]}''')
 
       self.view_funs.append(f'''\n\n<a name="{anchor}"></a>'''
@@ -165,6 +184,7 @@
     modules_dict[module_name] = ModuleMd(module_name, module_files)
 
   common_module = modules_dict.pop('common')
+  prelude_module = modules_dict.pop('prelude')
 
   with open(args.output, 'w') as f:
     f.write('''
@@ -197,6 +217,9 @@
 FROM android_startups;
 ```
 
+Prelude is a special module is automatically imported. It contains key helper
+tables, views and functions which are universally useful.
+
 More information on importing modules is available in the
 [syntax documentation](/docs/analysis/perfetto-sql-syntax#including-perfettosql-modules)
 for the `INCLUDE PERFETTO MODULE` statement.
@@ -206,23 +229,29 @@
 ## Summary
 ''')
 
-    summary_objs = [common_module.summary_objs
-                   ] if common_module.summary_objs else []
+    summary_objs = [prelude_module.summary_objs
+                   ] if prelude_module.summary_objs else []
+    summary_objs += [common_module.summary_objs
+                    ] if common_module.summary_objs else []
     summary_objs += [
         module.summary_objs
         for name, module in modules_dict.items()
         if (module.summary_objs and name != 'experimental')
     ]
 
-    summary_funs = [common_module.summary_funs
-                   ] if common_module.summary_funs else []
+    summary_funs = [prelude_module.summary_funs
+                   ] if prelude_module.summary_funs else []
+    summary_funs += [common_module.summary_funs
+                    ] if common_module.summary_funs else []
     summary_funs += [
         module.summary_funs
         for name, module in modules_dict.items()
         if (module.summary_funs and name != 'experimental')
     ]
-    summary_view_funs = [common_module.summary_view_funs
-                        ] if common_module.summary_view_funs else []
+    summary_view_funs = [prelude_module.summary_view_funs
+                        ] if prelude_module.summary_view_funs else []
+    summary_view_funs += [common_module.summary_view_funs
+                         ] if common_module.summary_view_funs else []
     summary_view_funs += [
         module.summary_view_funs
         for name, module in modules_dict.items()
@@ -251,6 +280,8 @@
       f.write('\n')
 
     f.write('\n\n')
+    f.write(prelude_module.print_description())
+    f.write('\n')
     f.write(common_module.print_description())
     f.write('\n')
     f.write('\n'.join(
diff --git a/python/generators/sql_processing/docs_parse.py b/python/generators/sql_processing/docs_parse.py
index 1c3142d..981316d 100644
--- a/python/generators/sql_processing/docs_parse.py
+++ b/python/generators/sql_processing/docs_parse.py
@@ -68,10 +68,11 @@
     if upper:
       module_pattern = module_pattern.upper()
     starts_with_module_name = re.match(module_pattern, self.name, re.IGNORECASE)
-    if self.module == "common":
+    if self.module == "common" or self.module == "prelude":
       if starts_with_module_name:
-        self._error('Names of tables/views/functions in the "common" module '
-                    f'should not start with {module_pattern}')
+        self._error(
+            'Names of tables/views/functions in the "{self.module}" module '
+            f'should not start with {module_pattern}')
       return self.name
     if not starts_with_module_name:
       self._error('Names of tables/views/functions should be prefixed with the '
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index 323d7bc..2667354 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -600,6 +600,7 @@
   trace_config_.reset(new TraceConfig());
 
   bool parsed = false;
+  bool cfg_could_be_txt = false;
   const bool will_trace_or_trigger = !is_attach() && !query_service_;
   if (!will_trace_or_trigger) {
     if ((!trace_config_raw.empty() || has_config_options)) {
@@ -625,6 +626,14 @@
                                      trace_config_.get());
     } else {
       parsed = trace_config_->ParseFromString(trace_config_raw);
+      cfg_could_be_txt =
+          !parsed && std::all_of(trace_config_raw.begin(),
+                                 trace_config_raw.end(), [](char c) {
+                                   // This is equiv to: isprint(c) || isspace(x)
+                                   // but doesn't depend on and load the locale.
+                                   return (c >= 32 && c <= 126) ||
+                                          (c >= 9 && c <= 13);
+                                 });
     }
   }
 
@@ -633,6 +642,12 @@
     trace_config_raw.clear();
   } else if (will_trace_or_trigger && !clone_tsid_) {
     PERFETTO_ELOG("The trace config is invalid, bailing out.");
+    if (cfg_could_be_txt) {
+      PERFETTO_ELOG(
+          "Looks like you are passing a textual config but I'm expecting a "
+          "proto-encoded binary config.");
+      PERFETTO_ELOG("Try adding --txt to the cmdline.");
+    }
     return 1;
   }
 
diff --git a/src/protozero/protoc_plugin/cppgen_plugin.cc b/src/protozero/protoc_plugin/cppgen_plugin.cc
index a33803f..f134caa 100644
--- a/src/protozero/protoc_plugin/cppgen_plugin.cc
+++ b/src/protozero/protoc_plugin/cppgen_plugin.cc
@@ -27,11 +27,7 @@
 #include <google/protobuf/compiler/code_generator.h>
 #include <google/protobuf/compiler/importer.h>
 #include <google/protobuf/compiler/plugin.h>
-#include <google/protobuf/dynamic_message.h>
 #include <google/protobuf/io/printer.h>
-#include <google/protobuf/io/zero_copy_stream_impl.h>
-#include <google/protobuf/util/field_comparator.h>
-#include <google/protobuf/util/message_differencer.h>
 
 #include "perfetto/ext/base/string_utils.h"
 
diff --git a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
index d9dc54d..c78d7f9 100644
--- a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
@@ -26,6 +26,7 @@
     "intervals",
     "linux",
     "pkvm",
+    "prelude",
     "sched",
   ]
   generated_header = "stdlib.h"
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn
new file mode 100644
index 0000000..19162d5
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright (C) 2023 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("prelude") {
+  sources = [ "slices.sql" ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql b/src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql
new file mode 100644
index 0000000..a669f04
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql
@@ -0,0 +1,32 @@
+--
+-- Copyright 2023 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.
+
+-- Given two slice ids, returns whether the first is an ancestor of the second.
+CREATE PERFETTO FUNCTION slice_is_ancestor(
+  -- Id of the potential ancestor slice.
+  ancestor_id LONG,
+  -- Id of the potential descendant slice.
+  descendant_id LONG
+)
+-- Whether `ancestor_id` slice is an ancestor of `descendant_id`.
+RETURNS BOOL AS
+SELECT
+  ancestor.track_id = descendant.track_id AND
+  ancestor.ts <= descendant.ts AND
+  (ancestor.dur == -1 OR ancestor.ts + ancestor.dur >= descendant.ts + descendant.dur)
+FROM slice ancestor
+JOIN slice descendant
+WHERE ancestor.id = $ancestor_id
+  AND descendant.id = $descendant_id
\ No newline at end of file
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 6c3b209..a24ac52 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -752,6 +752,7 @@
   // Initalize the tables and views in the prelude.
   InitializePreludeTablesViews(engine_->sqlite_engine()->db());
 
+  // Register stdlib modules.
   auto stdlib_modules = GetStdlibModules();
   for (auto module_it = stdlib_modules.GetIterator(); module_it; ++module_it) {
     base::Status status =
@@ -920,6 +921,16 @@
                                   *metric.proto_field_name);
     }
   }
+
+  // Import prelude module.
+  {
+    auto result = engine_->Execute(SqlSource::FromTraceProcessorImplementation(
+        "INCLUDE PERFETTO MODULE prelude.*"));
+    if (!result.status().ok()) {
+      PERFETTO_FATAL("Failed to import prelude: %s",
+                     result.status().c_message());
+    }
+  }
 }
 
 namespace {
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index d975a29..1e4783a 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -2013,6 +2013,9 @@
         if (!weak_this)
           return;
         TracingSession* session = weak_this->GetTracingSession(tsid);
+        if (!session) {
+          return;
+        }
         session->final_flush_outcome = success
                                            ? TraceStats::FINAL_FLUSH_SUCCEEDED
                                            : TraceStats::FINAL_FLUSH_FAILED;
diff --git a/src/tracing/core/tracing_service_impl_unittest.cc b/src/tracing/core/tracing_service_impl_unittest.cc
index fd578b8..c024c82 100644
--- a/src/tracing/core/tracing_service_impl_unittest.cc
+++ b/src/tracing/core/tracing_service_impl_unittest.cc
@@ -5158,4 +5158,55 @@
                                              Eq("B|1023|payload"))))));
 }
 
+// This is a regression test for https://b.corp.google.com/issues/307601836. The
+// test covers the case of a consumer disconnecting while the tracing session is
+// executing the final flush.
+TEST_F(TracingServiceImplTest, ConsumerDisconnectionRacesFlushAndDisable) {
+  std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+  consumer->Connect(svc.get());
+
+  std::unique_ptr<MockProducer> producer = CreateMockProducer();
+  producer->Connect(svc.get(), "mock_producer");
+
+  producer->RegisterDataSource("ds");
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(128);
+  auto* trigger_config = trace_config.mutable_trigger_config();
+  trigger_config->set_trigger_mode(TraceConfig::TriggerConfig::STOP_TRACING);
+  trigger_config->set_trigger_timeout_ms(100000);
+  auto* trigger = trigger_config->add_triggers();
+  trigger->set_name("trigger_name");
+  auto* ds_cfg = trace_config.add_data_sources()->mutable_config();
+  ds_cfg->set_name("ds");
+
+  consumer->EnableTracing(trace_config);
+  producer->WaitForTracingSetup();
+  producer->WaitForDataSourceSetup("ds");
+  producer->WaitForDataSourceStart("ds");
+
+  auto writer1 = producer->CreateTraceWriter("ds");
+
+  auto producer_flush_cb = [&](FlushRequestID flush_req_id,
+                               const DataSourceInstanceID* /*id*/, size_t,
+                               FlushFlags) {
+    // Notify the tracing service that the flush is complete.
+    producer->endpoint()->NotifyFlushComplete(flush_req_id);
+    // Also disconnect the consumer (this terminates the tracing session). The
+    // consumer disconnection is postponed with a PostTask(). The goal is to run
+    // the lambda inside TracingServiceImpl::FlushAndDisableTracing() with an
+    // empty `tracing_sessions_` map.
+    task_runner.PostTask([&]() { consumer.reset(); });
+  };
+  EXPECT_CALL(*producer, Flush(_, _, _, _)).WillOnce(Invoke(producer_flush_cb));
+
+  // Cause the tracing session to stop. Note that
+  // TracingServiceImpl::FlushAndDisableTracing() is also called when
+  // duration_ms expires, but in a test it's faster to use a trigger.
+  producer->endpoint()->ActivateTriggers({"trigger_name"});
+  producer->WaitForDataSourceStop("ds");
+
+  task_runner.RunUntilIdle();
+}
+
 }  // namespace perfetto
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 28c5abd..5def22b 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -99,6 +99,7 @@
 from diff_tests.stdlib.prelude.math_functions_tests import PreludeMathFunctions
 from diff_tests.stdlib.prelude.pprof_functions_tests import PreludePprofFunctions
 from diff_tests.stdlib.prelude.window_functions_tests import PreludeWindowFunctions
+from diff_tests.stdlib.prelude.slices_tests import PreludeSlices
 from diff_tests.stdlib.sched.tests import StdlibSched
 from diff_tests.stdlib.slices.tests import Slices
 from diff_tests.stdlib.span_join.tests_left_join import SpanJoinLeftJoin
@@ -232,6 +233,7 @@
       *PreludeWindowFunctions(index_path, 'stdlib/prelude',
                               'PreludeWindowFunctions').fetch(),
       *Pkvm(index_path, 'stdlib/pkvm', 'Pkvm').fetch(),
+      *PreludeSlices(index_path, 'stdlib/prelude', 'PreludeSlices').fetch(),
       *StdlibSmoke(index_path, 'stdlib', 'StdlibSmoke').fetch(),
       *StdlibCommon(index_path, 'stdlib/common', 'StdlibCommon').fetch(),
       *Slices(index_path, 'stdlib/slices', 'Slices').fetch(),
diff --git a/test/trace_processor/diff_tests/stdlib/prelude/nested_slices_trace.py b/test/trace_processor/diff_tests/stdlib/prelude/nested_slices_trace.py
new file mode 100644
index 0000000..43cc76a
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/prelude/nested_slices_trace.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+# Copyright (C) 2023 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 os import sys
+
+import synth_common
+
+from synth_common import ms_to_ns
+
+trace = synth_common.create_trace()
+
+track1_id = 1
+track2_id = 2
+
+trace.add_track_descriptor(track1_id)
+trace.add_track_descriptor(track2_id)
+
+trace.add_track_event_slice("Slice 1", ts=1, dur=10, track=track1_id)
+trace.add_track_event_slice("Slice 2", ts=2, dur=3, track=track1_id)
+trace.add_track_event_slice("Slice 3", ts=6, dur=3, track=track1_id)
+trace.add_track_event_slice("Slice 4", ts=3, dur=1, track=track2_id)
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/diff_tests/stdlib/prelude/slices_tests.py b/test/trace_processor/diff_tests/stdlib/prelude/slices_tests.py
new file mode 100644
index 0000000..793b713
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/prelude/slices_tests.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+# Copyright (C) 2023 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 Path, DataPath, Metric
+from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+from google.protobuf import text_format
+
+
+class PreludeSlices(TestSuite):
+
+  def test_slice_is_ancestor(self):
+    return DiffTestBlueprint(
+        trace=Path('nested_slices_trace.py'),
+        query="""
+        SELECT
+          s1.name, s2.name, slice_is_ancestor(s1.id, s2.id) AS is_ancestor
+        FROM slice s1
+        JOIN slice s2
+        WHERE s1.name < s2.name
+      """,
+        out=Csv("""
+        "name","name","is_ancestor"
+        "Slice 1","Slice 2",1
+        "Slice 1","Slice 4",0
+        "Slice 1","Slice 3",1
+        "Slice 2","Slice 4",0
+        "Slice 2","Slice 3",0
+        "Slice 3","Slice 4",0
+        """))
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index b5be573..28175b3 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -100,6 +100,7 @@
       name: STR_NULL,
       category: STR_NULL,
       id: NUM,
+      flowToDescendant: NUM,
     });
 
     const nullToStr = (s: null|string): string => {
@@ -155,6 +156,7 @@
         dur: it.endSliceStartTs - it.beginSliceEndTs,
         category,
         name,
+        flowToDescendant: !!it.flowToDescendant,
       });
     }
 
@@ -343,7 +345,8 @@
       (process_in.name || ' ' || process_in.pid) as endProcessName,
       extract_arg(f.arg_set_id, 'cat') as category,
       extract_arg(f.arg_set_id, 'name') as name,
-      f.id as id
+      f.id as id,
+      slice_is_ancestor(t1.slice_id, t2.slice_id) as flowToDescendant
     from ${connectedFlows} f
     join slice t1 on f.slice_out = t1.slice_id
     join slice t2 on f.slice_in = t2.slice_id
@@ -417,7 +420,8 @@
       NULL as endProcessName,
       extract_arg(f.arg_set_id, 'cat') as category,
       extract_arg(f.arg_set_id, 'name') as name,
-      f.id as id
+      f.id as id,
+      slice_is_ancestor(t1.slice_id, t2.slice_id) as flowToDescendant
     from flow f
     join slice t1 on f.slice_out = t1.slice_id
     join slice t2 on f.slice_in = t2.slice_id
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index f06704e..ed47282 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -214,8 +214,14 @@
       endDir = endYConnection.y > beginYConnection.y ? 'DOWN' : 'UP';
     }
 
+
     const begin = {
-      x: this.getXCoordinate(flow.begin.sliceEndTs),
+      // If the flow goes to a descendant, we want to draw the arrow from the
+      // beginning of the slice
+      // rather from the end to avoid the flow arrow going backwards.
+      x: this.getXCoordinate(
+          flow.flowToDescendant ? flow.begin.sliceStartTs :
+                                  flow.begin.sliceEndTs),
       y: beginYConnection.y,
       dir: beginDir,
     };
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 9a45e50..0affaed 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -124,6 +124,9 @@
   end: FlowPoint;
   dur: duration;
 
+  // Whether this flow connects a slice with its descendant.
+  flowToDescendant: boolean;
+
   category?: string;
   name?: string;
 }
diff --git a/ui/src/frontend/thread_state_tab.ts b/ui/src/frontend/thread_state_tab.ts
index 35d3b9f..a75ca4b 100644
--- a/ui/src/frontend/thread_state_tab.ts
+++ b/ui/src/frontend/thread_state_tab.ts
@@ -309,7 +309,7 @@
                       experimental_thread_executing_span_critical_path(
                         ${this.state?.thread?.utid},
                         trace_bounds.start_ts,
-                        trace_bounds.end_ts) cr,
+                        trace_bounds.end_ts - trace_bounds.start_ts) cr,
                       trace_bounds
                     JOIN thread USING(utid)
                     JOIN process USING(upid)
@@ -334,7 +334,7 @@
                         experimental_thread_executing_span_critical_path_stack(
                           ${this.state?.thread?.utid},
                           trace_bounds.start_ts,
-                          trace_bounds.end_ts) cr,
+                          trace_bounds.end_ts - trace_bounds.start_ts) cr,
                         trace_bounds WHERE name IS NOT NULL
                   `,
                   columns: sliceColumnNames,