code: def on_fit_end(self) -> None:
      if self.total_samples == 0:
          print_rank_0(
              "No samples are collected, skip trainer performance evaluation"
          )
          return
      avg_train_duration = all_reduce_mean(
          self.batch_timer.duration, self.world_size
      )
      avg_update_duration = all_reduce_mean(
          self.update_timer.duration, self.world_size
      )
      avg_episode_duration = all_reduce_mean(
          self.episode_timer.duration, self.world_size
      )
  
      avg_throughput = (
          self.total_samples
          * self.world_size
          / (avg_episode_duration + 1e-12)
      )
      avg_learn_tflops = (
          self.learn_flop / 1e12 / (avg_train_duration + 1e-12)
      )
      avg_time_per_sample = (avg_episode_duration + 1e-12) / (
          self.total_samples * self.world_size
      )
      avg_train_time_per_sample = (avg_train_duration + 1e-12) / (
          self.total_samples * self.world_size
      )
      avg_update_time_per_sample = (avg_update_duration + 1e-12) / (
          self.total_samples * self.world_size
      )
  
      print_rank_0(
          "Learning Performance Summary:\n"
          + f"Throughput: {avg_throughput:.3f} samples/sec\n"
          + f"TFLOPS per GPU: {avg_learn_tflops:.3f}\n"
          + f"Sample time (overall): {avg_time_per_sample:.3f} s\n"
          + f"Sample time (train): {avg_train_time_per_sample:.3f} s, {avg_train_time_per_sample/avg_time_per_sample*100:.2f}%\n"
          + f"Sample time (update): {avg_update_time_per_sample:.3f} s, {avg_update_time_per_sample/avg_time_per_sample*100:.2f}%\n"
      )
  
imports: from time import time
  from typing import Optional
  import torch
  import torch.distributed
  from coati.experience_maker import Experience
  from .base import MakerCallback, TrainerCallback
label_desc: Use image node_id 8 for calling the TrainerPerformanceEvaluator obj's underlying member method code with example usage: obj.on_fit_end() without return types