content: /*
   * 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
   */
  
  package com.android.server.backup.transport;
  
  import android.annotation.Nullable;
  import android.content.ComponentName;
  
  import java.io.PrintWriter;
  import java.util.HashMap;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Optional;
  
  /** Responsible for aggregating {@link TransportClient} relevant times. */
  public class TransportStats {
      private final Object mStatsLock = new Object();
      private final Map<ComponentName, Stats> mTransportStats = new HashMap<>();
  
      void registerConnectionTime(ComponentName transportComponent, long timeMs) {
          synchronized (mStatsLock) {
              Stats stats = mTransportStats.get(transportComponent);
              if (stats == null) {
                  stats = new Stats();
                  mTransportStats.put(transportComponent, stats);
              }
              stats.register(timeMs);
          }
      }
  
      /** Returns {@link Stats} for transport whose host service is {@code transportComponent}. */
      @Nullable
      public Stats getStatsForTransport(ComponentName transportComponent) {
          synchronized (mStatsLock) {
              Stats stats = mTransportStats.get(transportComponent);
              if (stats == null) {
                  return null;
              }
              return new Stats(stats);
          }
      }
  
      public void dump(PrintWriter pw) {
          synchronized (mStatsLock) {
              Optional<Stats> aggregatedStats =
                      mTransportStats.values().stream().reduce(Stats::merge);
              if (aggregatedStats.isPresent()) {
                  dumpStats(pw, "", aggregatedStats.get());
              }
              if (!mTransportStats.isEmpty()) {
                  pw.println("Per transport:");
                  for (ComponentName transportComponent : mTransportStats.keySet()) {
                      Stats stats = mTransportStats.get(transportComponent);
                      pw.println("    " + transportComponent.flattenToShortString());
                      dumpStats(pw, "        ", stats);
                  }
              }
          }
      }
  
      private static void dumpStats(PrintWriter pw, String prefix, Stats stats) {
          pw.println(
                  String.format(
                          Locale.US, "%sAverage connection time: %.2f ms", prefix, stats.average));
          pw.println(String.format(Locale.US, "%sMax connection time: %d ms", prefix, stats.max));
          pw.println(String.format(Locale.US, "%sMin connection time: %d ms", prefix, stats.min));
          pw.println(String.format(Locale.US, "%sNumber of connections: %d ", prefix, stats.n));
      }
  
      public static final class Stats {
          public static Stats merge(Stats a, Stats b) {
              return new Stats(
                      a.n + b.n,
                      (a.average * a.n + b.average * b.n) / (a.n + b.n),
                      Math.max(a.max, b.max),
                      Math.min(a.min, b.min));
          }
  
          public int n;
          public double average;
          public long max;
          public long min;
  
          public Stats() {
              n = 0;
              average = 0;
              max = 0;
              min = Long.MAX_VALUE;
          }
  
          private Stats(int n, double average, long max, long min) {
              this.n = n;
              this.average = average;
              this.max = max;
              this.min = min;
          }
  
          private Stats(Stats original) {
              this(original.n, original.average, original.max, original.min);
          }
  
          private void register(long sample) {
              average = (average * n + sample) / (n + 1);
              n++;
              max = Math.max(max, sample);
              min = Math.min(min, sample);
          }
      }
  }
  