Add AnalogGainStatsReporter to compute and report analog gain statistics

Implement AnalogGainStatsReporter and add it in AudioProcessingImpl.
This class computes statistics for analog gain updates and
periodically reports them into a histogram.

The added histograms for analog gain update statistics:

 - WebRTC.Audio.ApmAnalogGainDecreaseRate
 - WebRTC.Audio.ApmAnalogGainIncreaseRate
 - WebRTC.Audio.ApmAnalogGainUpdateRate
 - WebRTC.Audio.ApmAnalogGainDecreaseAverage
 - WebRTC.Audio.ApmAnalogGainIncreaseAverage
 - WebRTC.Audio.ApmAnalogGainUpdateAverage

The histograms are defined in
https://chromium-review.googlesource.com/c/chromium/src/+/3207987

Bug: webrtc:12774
Change-Id: I3c58d4bb3eb034a11c3f39ab8edb2bc67c5fd5e4
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/234140
Commit-Queue: Hanna Silen <silen@webrtc.org>
Reviewed-by: Per Ã…hgren <peah@webrtc.org>
Reviewed-by: Alessio Bazzica <alessiob@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35301}
diff --git a/modules/audio_processing/BUILD.gn b/modules/audio_processing/BUILD.gn
index 7fbae51..08574af 100644
--- a/modules/audio_processing/BUILD.gn
+++ b/modules/audio_processing/BUILD.gn
@@ -204,6 +204,7 @@
     "aec_dump:aec_dump",
     "aecm:aecm_core",
     "agc",
+    "agc:analog_gain_stats_reporter",
     "agc:gain_control_interface",
     "agc:legacy_agc",
     "capture_levels_adjuster",
diff --git a/modules/audio_processing/agc/BUILD.gn b/modules/audio_processing/agc/BUILD.gn
index 4bb8c54..d2a2563 100644
--- a/modules/audio_processing/agc/BUILD.gn
+++ b/modules/audio_processing/agc/BUILD.gn
@@ -41,6 +41,20 @@
   absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
 }
 
+rtc_library("analog_gain_stats_reporter") {
+  sources = [
+    "analog_gain_stats_reporter.cc",
+    "analog_gain_stats_reporter.h",
+  ]
+  deps = [
+    "../../../rtc_base:gtest_prod",
+    "../../../rtc_base:logging",
+    "../../../rtc_base:safe_minmax",
+    "../../../system_wrappers:metrics",
+  ]
+  absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
+}
+
 rtc_library("clipping_predictor") {
   sources = [
     "clipping_predictor.cc",
@@ -142,6 +156,7 @@
     testonly = true
     sources = [
       "agc_manager_direct_unittest.cc",
+      "analog_gain_stats_reporter_unittest.cc",
       "clipping_predictor_evaluator_unittest.cc",
       "clipping_predictor_level_buffer_unittest.cc",
       "clipping_predictor_unittest.cc",
@@ -152,6 +167,7 @@
 
     deps = [
       ":agc",
+      ":analog_gain_stats_reporter",
       ":clipping_predictor",
       ":clipping_predictor_evaluator",
       ":clipping_predictor_level_buffer",
@@ -161,6 +177,7 @@
       "../../../rtc_base:checks",
       "../../../rtc_base:rtc_base_approved",
       "../../../rtc_base:safe_conversions",
+      "../../../system_wrappers:metrics",
       "../../../test:field_trial",
       "../../../test:fileutils",
       "../../../test:test_support",
diff --git a/modules/audio_processing/agc/analog_gain_stats_reporter.cc b/modules/audio_processing/agc/analog_gain_stats_reporter.cc
new file mode 100644
index 0000000..0d8753a
--- /dev/null
+++ b/modules/audio_processing/agc/analog_gain_stats_reporter.cc
@@ -0,0 +1,130 @@
+/*
+ *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/audio_processing/agc/analog_gain_stats_reporter.h"
+
+#include <cmath>
+
+#include "rtc_base/logging.h"
+#include "rtc_base/numerics/safe_minmax.h"
+#include "system_wrappers/include/metrics.h"
+
+namespace webrtc {
+namespace {
+
+constexpr int kFramesIn60Seconds = 6000;
+constexpr int kMinGain = 0;
+constexpr int kMaxGain = 255;
+constexpr int kMaxUpdate = kMaxGain - kMinGain;
+
+float ComputeAverageUpdate(int sum_updates, int num_updates) {
+  RTC_DCHECK_GE(sum_updates, 0);
+  RTC_DCHECK_LE(sum_updates, kMaxUpdate * kFramesIn60Seconds);
+  RTC_DCHECK_GE(num_updates, 0);
+  RTC_DCHECK_LE(num_updates, kFramesIn60Seconds);
+  if (num_updates == 0) {
+    return 0.0f;
+  }
+  return std::round(static_cast<float>(sum_updates) /
+                    static_cast<float>(num_updates));
+}
+}  // namespace
+
+AnalogGainStatsReporter::AnalogGainStatsReporter() = default;
+
+AnalogGainStatsReporter::~AnalogGainStatsReporter() = default;
+
+void AnalogGainStatsReporter::UpdateStatistics(int analog_mic_level) {
+  RTC_DCHECK_GE(analog_mic_level, kMinGain);
+  RTC_DCHECK_LE(analog_mic_level, kMaxGain);
+  if (previous_analog_mic_level_.has_value() &&
+      analog_mic_level != previous_analog_mic_level_.value()) {
+    const int level_change =
+        analog_mic_level - previous_analog_mic_level_.value();
+    if (level_change < 0) {
+      ++level_update_stats_.num_decreases;
+      level_update_stats_.sum_decreases -= level_change;
+    } else {
+      ++level_update_stats_.num_increases;
+      level_update_stats_.sum_increases += level_change;
+    }
+  }
+  // Periodically log analog gain change metrics.
+  if (++log_level_update_stats_counter_ >= kFramesIn60Seconds) {
+    LogLevelUpdateStats();
+    level_update_stats_ = {};
+    log_level_update_stats_counter_ = 0;
+  }
+  previous_analog_mic_level_ = analog_mic_level;
+}
+
+void AnalogGainStatsReporter::LogLevelUpdateStats() const {
+  const float average_decrease = ComputeAverageUpdate(
+      level_update_stats_.sum_decreases, level_update_stats_.num_decreases);
+  const float average_increase = ComputeAverageUpdate(
+      level_update_stats_.sum_increases, level_update_stats_.num_increases);
+  const int num_updates =
+      level_update_stats_.num_decreases + level_update_stats_.num_increases;
+  const float average_update = ComputeAverageUpdate(
+      level_update_stats_.sum_decreases + level_update_stats_.sum_increases,
+      num_updates);
+  RTC_DLOG(LS_INFO) << "Analog gain update rate: "
+                    << "num_updates=" << num_updates
+                    << ", num_decreases=" << level_update_stats_.num_decreases
+                    << ", num_increases=" << level_update_stats_.num_increases;
+  RTC_DLOG(LS_INFO) << "Analog gain update average: "
+                    << "average_update=" << average_update
+                    << ", average_decrease=" << average_decrease
+                    << ", average_increase=" << average_increase;
+  RTC_HISTOGRAM_COUNTS_LINEAR(
+      /*name=*/"WebRTC.Audio.ApmAnalogGainDecreaseRate",
+      /*sample=*/level_update_stats_.num_decreases,
+      /*min=*/1,
+      /*max=*/kFramesIn60Seconds,
+      /*bucket_count=*/50);
+  if (level_update_stats_.num_decreases > 0) {
+    RTC_HISTOGRAM_COUNTS_LINEAR(
+        /*name=*/"WebRTC.Audio.ApmAnalogGainDecreaseAverage",
+        /*sample=*/average_decrease,
+        /*min=*/1,
+        /*max=*/kMaxUpdate,
+        /*bucket_count=*/50);
+  }
+  RTC_HISTOGRAM_COUNTS_LINEAR(
+      /*name=*/"WebRTC.Audio.ApmAnalogGainIncreaseRate",
+      /*sample=*/level_update_stats_.num_increases,
+      /*min=*/1,
+      /*max=*/kFramesIn60Seconds,
+      /*bucket_count=*/50);
+  if (level_update_stats_.num_increases > 0) {
+    RTC_HISTOGRAM_COUNTS_LINEAR(
+        /*name=*/"WebRTC.Audio.ApmAnalogGainIncreaseAverage",
+        /*sample=*/average_increase,
+        /*min=*/1,
+        /*max=*/kMaxUpdate,
+        /*bucket_count=*/50);
+  }
+  RTC_HISTOGRAM_COUNTS_LINEAR(
+      /*name=*/"WebRTC.Audio.ApmAnalogGainUpdateRate",
+      /*sample=*/num_updates,
+      /*min=*/1,
+      /*max=*/kFramesIn60Seconds,
+      /*bucket_count=*/50);
+  if (num_updates > 0) {
+    RTC_HISTOGRAM_COUNTS_LINEAR(
+        /*name=*/"WebRTC.Audio.ApmAnalogGainUpdateAverage",
+        /*sample=*/average_update,
+        /*min=*/1,
+        /*max=*/kMaxUpdate,
+        /*bucket_count=*/50);
+  }
+}
+
+}  // namespace webrtc
diff --git a/modules/audio_processing/agc/analog_gain_stats_reporter.h b/modules/audio_processing/agc/analog_gain_stats_reporter.h
new file mode 100644
index 0000000..c9442e8
--- /dev/null
+++ b/modules/audio_processing/agc/analog_gain_stats_reporter.h
@@ -0,0 +1,67 @@
+/*
+ *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_AUDIO_PROCESSING_AGC_ANALOG_GAIN_STATS_REPORTER_H_
+#define MODULES_AUDIO_PROCESSING_AGC_ANALOG_GAIN_STATS_REPORTER_H_
+
+#include "absl/types/optional.h"
+#include "rtc_base/gtest_prod_util.h"
+
+namespace webrtc {
+
+// Analog gain statistics calculator. Computes aggregate stats based on the
+// framewise mic levels processed in `UpdateStatistics()`. Periodically logs the
+// statistics into a histogram.
+class AnalogGainStatsReporter {
+ public:
+  AnalogGainStatsReporter();
+  AnalogGainStatsReporter(const AnalogGainStatsReporter&) = delete;
+  AnalogGainStatsReporter operator=(const AnalogGainStatsReporter&) = delete;
+  ~AnalogGainStatsReporter();
+
+  // Updates the stats based on the `analog_mic_level`. Periodically logs the
+  // stats into a histogram.
+  void UpdateStatistics(int analog_mic_level);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(AnalogGainStatsReporterTest,
+                           CheckLevelUpdateStatsForEmptyStats);
+  FRIEND_TEST_ALL_PREFIXES(AnalogGainStatsReporterTest,
+                           CheckLevelUpdateStatsAfterNoGainChange);
+  FRIEND_TEST_ALL_PREFIXES(AnalogGainStatsReporterTest,
+                           CheckLevelUpdateStatsAfterGainIncrease);
+  FRIEND_TEST_ALL_PREFIXES(AnalogGainStatsReporterTest,
+                           CheckLevelUpdateStatsAfterGainDecrease);
+  FRIEND_TEST_ALL_PREFIXES(AnalogGainStatsReporterTest,
+                           CheckLevelUpdateStatsAfterReset);
+
+  // Stores analog gain update stats to enable calculation of update rate and
+  // average update separately for gain increases and decreases.
+  struct LevelUpdateStats {
+    int num_decreases = 0;
+    int num_increases = 0;
+    int sum_decreases = 0;
+    int sum_increases = 0;
+  } level_update_stats_;
+
+  // Returns a copy of the stored statistics. Use only for testing.
+  const LevelUpdateStats level_update_stats() const {
+    return level_update_stats_;
+  }
+
+  // Computes aggregate stat and logs them into a histogram.
+  void LogLevelUpdateStats() const;
+
+  int log_level_update_stats_counter_ = 0;
+  absl::optional<int> previous_analog_mic_level_ = absl::nullopt;
+};
+}  // namespace webrtc
+
+#endif  // MODULES_AUDIO_PROCESSING_AGC_ANALOG_GAIN_STATS_REPORTER_H_
diff --git a/modules/audio_processing/agc/analog_gain_stats_reporter_unittest.cc b/modules/audio_processing/agc/analog_gain_stats_reporter_unittest.cc
new file mode 100644
index 0000000..cab5287
--- /dev/null
+++ b/modules/audio_processing/agc/analog_gain_stats_reporter_unittest.cc
@@ -0,0 +1,161 @@
+/*
+ *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/audio_processing/agc/analog_gain_stats_reporter.h"
+
+#include "system_wrappers/include/metrics.h"
+#include "test/gmock.h"
+
+namespace webrtc {
+namespace {
+
+constexpr int kFramesIn60Seconds = 6000;
+
+TEST(AnalogGainStatsReporterTest, CheckLogLevelUpdateStatsEmpty) {
+  AnalogGainStatsReporter stats_reporter;
+  constexpr int kMicLevel = 10;
+  stats_reporter.UpdateStatistics(kMicLevel);
+  // Update almost until the periodic logging and reset.
+  for (int i = 0; i < kFramesIn60Seconds - 2; i += 2) {
+    stats_reporter.UpdateStatistics(kMicLevel + 2);
+    stats_reporter.UpdateStatistics(kMicLevel);
+  }
+  EXPECT_METRIC_THAT(metrics::Samples("WebRTC.Audio.ApmAnalogGainUpdateRate"),
+                     ::testing::ElementsAre());
+  EXPECT_METRIC_THAT(metrics::Samples("WebRTC.Audio.ApmAnalogGainDecreaseRate"),
+                     ::testing::ElementsAre());
+  EXPECT_METRIC_THAT(metrics::Samples("WebRTC.Audio.ApmAnalogGainIncreaseRate"),
+                     ::testing::ElementsAre());
+  EXPECT_METRIC_THAT(
+      metrics::Samples("WebRTC.Audio.ApmAnalogGainUpdateAverage"),
+      ::testing::ElementsAre());
+  EXPECT_METRIC_THAT(
+      metrics::Samples("WebRTC.Audio.ApmAnalogGainDecreaseAverage"),
+      ::testing::ElementsAre());
+  EXPECT_METRIC_THAT(
+      metrics::Samples("WebRTC.Audio.ApmAnalogGainIncreaseAverage"),
+      ::testing::ElementsAre());
+}
+
+TEST(AnalogGainStatsReporterTest, CheckLogLevelUpdateStatsNotEmpty) {
+  AnalogGainStatsReporter stats_reporter;
+  constexpr int kMicLevel = 10;
+  stats_reporter.UpdateStatistics(kMicLevel);
+  // Update until periodic logging.
+  for (int i = 0; i < kFramesIn60Seconds; i += 2) {
+    stats_reporter.UpdateStatistics(kMicLevel + 2);
+    stats_reporter.UpdateStatistics(kMicLevel);
+  }
+  // Update until periodic logging.
+  for (int i = 0; i < kFramesIn60Seconds; i += 2) {
+    stats_reporter.UpdateStatistics(kMicLevel + 3);
+    stats_reporter.UpdateStatistics(kMicLevel);
+  }
+  EXPECT_METRIC_THAT(
+      metrics::Samples("WebRTC.Audio.ApmAnalogGainUpdateRate"),
+      ::testing::ElementsAre(::testing::Pair(kFramesIn60Seconds - 1, 1),
+                             ::testing::Pair(kFramesIn60Seconds, 1)));
+  EXPECT_METRIC_THAT(
+      metrics::Samples("WebRTC.Audio.ApmAnalogGainDecreaseRate"),
+      ::testing::ElementsAre(::testing::Pair(kFramesIn60Seconds / 2 - 1, 1),
+                             ::testing::Pair(kFramesIn60Seconds / 2, 1)));
+  EXPECT_METRIC_THAT(
+      metrics::Samples("WebRTC.Audio.ApmAnalogGainIncreaseRate"),
+      ::testing::ElementsAre(::testing::Pair(kFramesIn60Seconds / 2, 2)));
+  EXPECT_METRIC_THAT(
+      metrics::Samples("WebRTC.Audio.ApmAnalogGainUpdateAverage"),
+      ::testing::ElementsAre(::testing::Pair(2, 1), ::testing::Pair(3, 1)));
+  EXPECT_METRIC_THAT(
+      metrics::Samples("WebRTC.Audio.ApmAnalogGainDecreaseAverage"),
+      ::testing::ElementsAre(::testing::Pair(2, 1), ::testing::Pair(3, 1)));
+  EXPECT_METRIC_THAT(
+      metrics::Samples("WebRTC.Audio.ApmAnalogGainIncreaseAverage"),
+      ::testing::ElementsAre(::testing::Pair(2, 1), ::testing::Pair(3, 1)));
+}
+}  // namespace
+
+TEST(AnalogGainStatsReporterTest, CheckLevelUpdateStatsForEmptyStats) {
+  AnalogGainStatsReporter stats_reporter;
+  const auto& update_stats = stats_reporter.level_update_stats();
+  EXPECT_EQ(update_stats.num_decreases, 0);
+  EXPECT_EQ(update_stats.sum_decreases, 0);
+  EXPECT_EQ(update_stats.num_increases, 0);
+  EXPECT_EQ(update_stats.sum_increases, 0);
+}
+
+TEST(AnalogGainStatsReporterTest, CheckLevelUpdateStatsAfterNoGainChange) {
+  constexpr int kMicLevel = 10;
+  AnalogGainStatsReporter stats_reporter;
+  stats_reporter.UpdateStatistics(kMicLevel);
+  stats_reporter.UpdateStatistics(kMicLevel);
+  stats_reporter.UpdateStatistics(kMicLevel);
+  const auto& update_stats = stats_reporter.level_update_stats();
+  EXPECT_EQ(update_stats.num_decreases, 0);
+  EXPECT_EQ(update_stats.sum_decreases, 0);
+  EXPECT_EQ(update_stats.num_increases, 0);
+  EXPECT_EQ(update_stats.sum_increases, 0);
+}
+
+TEST(AnalogGainStatsReporterTest, CheckLevelUpdateStatsAfterGainIncrease) {
+  constexpr int kMicLevel = 10;
+  AnalogGainStatsReporter stats_reporter;
+  stats_reporter.UpdateStatistics(kMicLevel);
+  stats_reporter.UpdateStatistics(kMicLevel + 4);
+  stats_reporter.UpdateStatistics(kMicLevel + 5);
+  const auto& update_stats = stats_reporter.level_update_stats();
+  EXPECT_EQ(update_stats.num_decreases, 0);
+  EXPECT_EQ(update_stats.sum_decreases, 0);
+  EXPECT_EQ(update_stats.num_increases, 2);
+  EXPECT_EQ(update_stats.sum_increases, 5);
+}
+
+TEST(AnalogGainStatsReporterTest, CheckLevelUpdateStatsAfterGainDecrease) {
+  constexpr int kMicLevel = 10;
+  AnalogGainStatsReporter stats_reporter;
+  stats_reporter.UpdateStatistics(kMicLevel);
+  stats_reporter.UpdateStatistics(kMicLevel - 4);
+  stats_reporter.UpdateStatistics(kMicLevel - 5);
+  const auto& stats_update = stats_reporter.level_update_stats();
+  EXPECT_EQ(stats_update.num_decreases, 2);
+  EXPECT_EQ(stats_update.sum_decreases, 5);
+  EXPECT_EQ(stats_update.num_increases, 0);
+  EXPECT_EQ(stats_update.sum_increases, 0);
+}
+
+TEST(AnalogGainStatsReporterTest, CheckLevelUpdateStatsAfterReset) {
+  AnalogGainStatsReporter stats_reporter;
+  constexpr int kMicLevel = 10;
+  stats_reporter.UpdateStatistics(kMicLevel);
+  // Update until the periodic reset.
+  for (int i = 0; i < kFramesIn60Seconds - 2; i += 2) {
+    stats_reporter.UpdateStatistics(kMicLevel + 2);
+    stats_reporter.UpdateStatistics(kMicLevel);
+  }
+  const auto& stats_before_reset = stats_reporter.level_update_stats();
+  EXPECT_EQ(stats_before_reset.num_decreases, kFramesIn60Seconds / 2 - 1);
+  EXPECT_EQ(stats_before_reset.sum_decreases, kFramesIn60Seconds - 2);
+  EXPECT_EQ(stats_before_reset.num_increases, kFramesIn60Seconds / 2 - 1);
+  EXPECT_EQ(stats_before_reset.sum_increases, kFramesIn60Seconds - 2);
+  stats_reporter.UpdateStatistics(kMicLevel + 2);
+  const auto& stats_during_reset = stats_reporter.level_update_stats();
+  EXPECT_EQ(stats_during_reset.num_decreases, 0);
+  EXPECT_EQ(stats_during_reset.sum_decreases, 0);
+  EXPECT_EQ(stats_during_reset.num_increases, 0);
+  EXPECT_EQ(stats_during_reset.sum_increases, 0);
+  stats_reporter.UpdateStatistics(kMicLevel);
+  stats_reporter.UpdateStatistics(kMicLevel + 3);
+  const auto& stats_after_reset = stats_reporter.level_update_stats();
+  EXPECT_EQ(stats_after_reset.num_decreases, 1);
+  EXPECT_EQ(stats_after_reset.sum_decreases, 2);
+  EXPECT_EQ(stats_after_reset.num_increases, 1);
+  EXPECT_EQ(stats_after_reset.sum_increases, 3);
+}
+
+}  // namespace webrtc
diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc
index 2b02a23..169ffe4 100644
--- a/modules/audio_processing/audio_processing_impl.cc
+++ b/modules/audio_processing/audio_processing_impl.cc
@@ -1149,6 +1149,7 @@
       capture_.prev_analog_mic_level != analog_mic_level &&
       capture_.prev_analog_mic_level != -1;
   capture_.prev_analog_mic_level = analog_mic_level;
+  analog_gain_stats_reporter_.UpdateStatistics(analog_mic_level);
 
   if (submodules_.echo_controller) {
     capture_.echo_path_gain_change = analog_mic_level_changed;
diff --git a/modules/audio_processing/audio_processing_impl.h b/modules/audio_processing/audio_processing_impl.h
index bf1d22e..22cdadd 100644
--- a/modules/audio_processing/audio_processing_impl.h
+++ b/modules/audio_processing/audio_processing_impl.h
@@ -21,6 +21,7 @@
 #include "api/function_view.h"
 #include "modules/audio_processing/aec3/echo_canceller3.h"
 #include "modules/audio_processing/agc/agc_manager_direct.h"
+#include "modules/audio_processing/agc/analog_gain_stats_reporter.h"
 #include "modules/audio_processing/agc/gain_control.h"
 #include "modules/audio_processing/audio_buffer.h"
 #include "modules/audio_processing/capture_levels_adjuster/capture_levels_adjuster.h"
@@ -531,6 +532,9 @@
   RmsLevel capture_output_rms_ RTC_GUARDED_BY(mutex_capture_);
   int capture_rms_interval_counter_ RTC_GUARDED_BY(mutex_capture_) = 0;
 
+  AnalogGainStatsReporter analog_gain_stats_reporter_
+      RTC_GUARDED_BY(mutex_capture_);
+
   // Lock protection not needed.
   std::unique_ptr<
       SwapQueue<std::vector<int16_t>, RenderQueueItemVerifier<int16_t>>>