// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/audio/input_glitch_counter.h"

#include <cinttypes>
#include <cstddef>
#include <utility>

#include "base/check_op.h"
#include "base/format_macros.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"

namespace audio {

namespace {

// Used to log if any audio glitches have been detected during an audio session.
// Elements in this enum should not be added, deleted or rearranged.
enum class AudioGlitchResult {
  kNoGlitches = 0,
  kGlitches = 1,
  kMaxValue = kGlitches
};

}  // namespace

InputGlitchCounter::InputGlitchCounter(
    base::RepeatingCallback<void(const std::string&)> log_callback)
    : id_(base::UnguessableToken::Create()),
      log_callback_(std::move(log_callback)) {
  // Reserve one minutes worth of complete samples (assuming 10ms buffers) to
  // hopefully avoid allocating them on the realtime thread.
  complete_samples_.reserve(6);
}

InputGlitchCounter::~InputGlitchCounter() {
  // Subtract 'trailing' error counts that will happen if the renderer process
  // was killed or e.g. the page refreshed while the input device was open etc.
  // The counts should then only include data from before the teardown period.
  DCHECK_GE(write_count_, global_sample_.trailing_missed_read_deadline_count_);
  DCHECK_GE(global_sample_.missed_read_deadline_count_,
            global_sample_.trailing_missed_read_deadline_count_);
  DCHECK_GE(global_sample_.dropped_data_count_,
            global_sample_.trailing_dropped_data_count_);
  write_count_ -= global_sample_.trailing_missed_read_deadline_count_;
  global_sample_.missed_read_deadline_count_ -=
      global_sample_.trailing_missed_read_deadline_count_;
  global_sample_.dropped_data_count_ -=
      global_sample_.trailing_dropped_data_count_;

  // Log whether or not there was any dropped data at all (before the teardown
  // period).
  base::UmaHistogramEnumeration("Media.AudioCapturerAudioGlitches",
                                global_sample_.dropped_data_count_ == 0
                                    ? AudioGlitchResult::kNoGlitches
                                    : AudioGlitchResult::kGlitches);

  std::string log_string = base::StringPrintf(
      "AISW::%s => (number of detected audio glitches: %" PRIu64
      " out of %" PRIu64
      ") "
      " [id=%s]",
      __func__, global_sample_.dropped_data_count_, write_count_,
      id_.ToString().c_str());
  log_callback_.Run(log_string);

  if (write_count_ < kSampleInterval) {
    // The stream without trailing glitches is shorter than kSampleInterval
    // callbacks, which means UploadCompleteSamples() has never uploaded
    // anything. Adding stats to dedicated histograms.
    base::UmaHistogramCounts1000("Media.AudioCapturerDroppedDataBelow10s",
                                 global_sample_.dropped_data_count_);
    base::UmaHistogramCounts1000(
        "Media.AudioCapturerMissedReadDeadlineBelow10s",
        global_sample_.missed_read_deadline_count_);
  }

  // All remaining not uploaded |complete_samples_| will be dropped, since the
  // first of them at least partially and the rest fully consist of trailing
  // glitches.
}

void InputGlitchCounter::ReportDroppedData(bool dropped_data) {
  if (dropped_data) {
    ++current_sample_.dropped_data_count_;
    ++global_sample_.dropped_data_count_;
    ++global_sample_.trailing_dropped_data_count_;
  } else {
    // Since we have just successfully written data, we can assume that the
    // receiver is still alive, so there should not be any trailing errors.
    // Thererfore we can upload any complete samples we have stored.
    global_sample_.trailing_dropped_data_count_ = 0;
    global_sample_.trailing_missed_read_deadline_count_ = 0;
    UploadCompleteSamples();
  }
}

void InputGlitchCounter::ReportMissedReadDeadline(bool missed_read_deadline) {
  ++write_count_;
  if (missed_read_deadline) {
    ++current_sample_.missed_read_deadline_count_;
    ++global_sample_.missed_read_deadline_count_;
    ++global_sample_.trailing_missed_read_deadline_count_;
  }

  // If it's the end of the sample window, then it's time queue the current
  // sample to be uploaded and start adding data to a new sample.
  if (write_count_ % kSampleInterval == 0) {
    // The sample will be uploaded once we know that it does not contain
    // trailing errors.
    complete_samples_.push_back(current_sample_);
    current_sample_ = {};
  }
}

void InputGlitchCounter::UploadCompleteSamples() {
  for (Sample& sample : complete_samples_) {
    base::UmaHistogramCounts1000("Media.AudioCapturerDroppedData10sIntervals",
                                 sample.dropped_data_count_);
    base::UmaHistogramCounts1000(
        "Media.AudioCapturerMissedReadDeadline10sIntervals",
        sample.missed_read_deadline_count_);
  }
  complete_samples_.clear();
}

}  // namespace audio
