/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */


#include "pagespeed/opt/logging/request_timing_info.h"

#include "base/logging.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/timer.h"

namespace net_instaweb {

namespace {

bool SetValueIfGEZero(int64 in, int64* out) {
  if (in < 0) {
    return false;
  }

  *out = in;
  return true;
}

}  // namespace


RequestTimingInfo::RequestTimingInfo(Timer* timer, AbstractMutex* mutex)
    : timer_(timer),
      init_ts_ms_(-1),
      start_ts_ms_(-1),
      processing_start_ts_ms_(-1),
      pcache_lookup_start_ts_ms_(-1),
      pcache_lookup_end_ts_ms_(-1),
      parsing_start_ts_ms_(-1),
      end_ts_ms_(-1),
      mu_(mutex),
      fetch_start_ts_ms_(-1),
      fetch_header_ts_ms_(-1),
      fetch_end_ts_ms_(-1),
      first_byte_ts_ms_(-1),
      http_cache_latency_ms_(-1),
      l2http_cache_latency_ms_(-1) {
  init_ts_ms_ = NowMs();
}

void RequestTimingInfo::RequestStarted() {
  SetToNow(&start_ts_ms_);
  VLOG(2) << "RequestStarted: " << start_ts_ms_;
}

void RequestTimingInfo::FirstByteReturned() {
  ScopedMutex l(mu_);
  SetToNow(&first_byte_ts_ms_);
}

void RequestTimingInfo::FetchStarted() {
  ScopedMutex l(mu_);
  if (fetch_start_ts_ms_ > 0) {
    // It's possible this is called more than once, just ignore subsequent
    // calls.
    return;
  }

  SetToNow(&fetch_start_ts_ms_);
}

void RequestTimingInfo::FetchHeaderReceived() {
  ScopedMutex l(mu_);
  SetToNow(&fetch_header_ts_ms_);
}

void RequestTimingInfo::FetchFinished() {
  ScopedMutex l(mu_);
  SetToNow(&fetch_end_ts_ms_);
}

void RequestTimingInfo::SetHTTPCacheLatencyMs(int64 latency_ms) {
  ScopedMutex l(mu_);
  SetValueIfGEZero(latency_ms, &http_cache_latency_ms_);
}

void RequestTimingInfo::SetL2HTTPCacheLatencyMs(int64 latency_ms) {
  ScopedMutex l(mu_);
  SetValueIfGEZero(latency_ms, &l2http_cache_latency_ms_);
}

int64 RequestTimingInfo::GetElapsedMs() const {
  DCHECK_GE(init_ts_ms_, 0);
  return NowMs() - init_ts_ms_;
}

bool RequestTimingInfo::GetProcessingElapsedMs(
    int64* processing_elapsed_ms) const {
  if (end_ts_ms_ < 0 || start_ts_ms_ < 0) {
    return false;
  }
  int64 elapsed_ms = end_ts_ms_ - start_ts_ms_;
  int64 fetch_elapsed_ms = 0;
  if (GetFetchLatencyMs(&fetch_elapsed_ms)) {
    elapsed_ms -= fetch_elapsed_ms;
  }

  *processing_elapsed_ms = elapsed_ms;
  return true;
}

bool RequestTimingInfo::GetTimeToStartFetchMs(
    int64* elapsed_ms) const {
  ScopedMutex l(mu_);
  return GetTimeFromStart(fetch_start_ts_ms_, elapsed_ms);
}

bool RequestTimingInfo::GetFetchHeaderLatencyMs(
    int64* elapsed_ms) const {
  ScopedMutex l(mu_);
  if (fetch_header_ts_ms_ < 0 || fetch_start_ts_ms_< 0) {
    return false;
  }

  const int64 tmp_elapsed_ms = fetch_header_ts_ms_ - fetch_start_ts_ms_;
  if (tmp_elapsed_ms < 0) {
    return false;
  }

  *elapsed_ms = tmp_elapsed_ms;
  return true;
}

bool RequestTimingInfo::GetFetchLatencyMs(int64* elapsed_ms) const {
  ScopedMutex l(mu_);
  if (fetch_end_ts_ms_ < 0 || fetch_start_ts_ms_ < 0) {
    return false;
  }

  *elapsed_ms = fetch_end_ts_ms_ - fetch_start_ts_ms_;
  return true;
}

bool RequestTimingInfo::GetTimeToFirstByte(int64* latency_ms) const {
  ScopedMutex l(mu_);
  if (first_byte_ts_ms_ < 0) {
    return false;
  }

  *latency_ms = first_byte_ts_ms_ - init_ts_ms_;
  return true;
}

int64 RequestTimingInfo::NowMs() const {
  if (timer_ == NULL) {
    return 0;
  }

  return timer_->NowMs();
}

void RequestTimingInfo::SetToNow(int64* ts) const {
  DCHECK_GE(*ts, -1);
  *ts = NowMs();
}

bool RequestTimingInfo::GetTimeFromStart(
    int64 ts_ms, int64* elapsed_ms) const {
  if (ts_ms < 0 || start_ts_ms_ < 0) {
    return false;
  }

  *elapsed_ms = ts_ms - start_ts_ms_;
  return true;
}

bool RequestTimingInfo::GetHTTPCacheLatencyMs(
    int64* latency_ms) const {
  ScopedMutex l(mu_);
  return SetValueIfGEZero(http_cache_latency_ms_, latency_ms);
}

bool RequestTimingInfo::GetL2HTTPCacheLatencyMs(
    int64* latency_ms) const {
  ScopedMutex l(mu_);
  return SetValueIfGEZero(l2http_cache_latency_ms_, latency_ms);
}

}  // namespace net_instaweb
