/*
 * 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.
 */

#include <limits>

#include <stdint.h>

#include "src/trace_processor/process_tracker.h"
#include "src/trace_processor/slice_tracker.h"
#include "src/trace_processor/trace_processor_context.h"
#include "src/trace_processor/trace_storage.h"

namespace perfetto {
namespace trace_processor {

SliceTracker::SliceTracker(TraceProcessorContext* context)
    : context_(context) {}

SliceTracker::~SliceTracker() = default;

void SliceTracker::BeginAndroid(int64_t timestamp,
                                uint32_t ftrace_tid,
                                uint32_t atrace_tid,
                                StringId cat,
                                StringId name) {
  UniqueTid utid =
      context_->process_tracker->UpdateThread(timestamp, atrace_tid, 0);
  ftrace_to_atrace_pid_[ftrace_tid] = atrace_tid;
  Begin(timestamp, utid, cat, name);
}

void SliceTracker::Begin(int64_t timestamp,
                         UniqueTid utid,
                         StringId cat,
                         StringId name) {
  MaybeCloseStack(timestamp, &threads_[utid]);
  StartSlice(timestamp, 0, utid, cat, name);
}

void SliceTracker::Scoped(int64_t timestamp,
                          UniqueTid utid,
                          StringId cat,
                          StringId name,
                          int64_t duration) {
  MaybeCloseStack(timestamp, &threads_[utid]);
  StartSlice(timestamp, duration, utid, cat, name);
}

void SliceTracker::StartSlice(int64_t timestamp,
                              int64_t duration,
                              UniqueTid utid,
                              StringId cat,
                              StringId name) {
  auto* stack = &threads_[utid];
  auto* slices = context_->storage->mutable_nestable_slices();

  const uint8_t depth = static_cast<uint8_t>(stack->size());
  if (depth >= std::numeric_limits<uint8_t>::max()) {
    PERFETTO_DFATAL("Slices with too large depth found.");
    return;
  }
  int64_t parent_stack_id = depth == 0 ? 0 : slices->stack_ids()[stack->back()];
  size_t slice_idx = slices->AddSlice(timestamp, duration, utid, cat, name,
                                      depth, 0, parent_stack_id);
  stack->emplace_back(slice_idx);

  slices->set_stack_id(slice_idx, GetStackHash(*stack));
}

void SliceTracker::EndAndroid(int64_t timestamp,
                              uint32_t ftrace_tid,
                              uint32_t atrace_tid) {
  uint32_t actual_tid = ftrace_to_atrace_pid_[ftrace_tid];
  if (atrace_tid != 0 && atrace_tid != actual_tid) {
    PERFETTO_DLOG("Mismatched atrace pid %u and looked up pid %u", atrace_tid,
                  actual_tid);
  }
  if (actual_tid == 0)
    return;
  UniqueTid utid =
      context_->process_tracker->UpdateThread(timestamp, actual_tid, 0);
  End(timestamp, utid);
}

void SliceTracker::End(int64_t timestamp,
                       UniqueTid utid,
                       StringId cat,
                       StringId name) {
  MaybeCloseStack(timestamp, &threads_[utid]);

  const auto& stack = threads_[utid];
  if (stack.empty())
    return;

  auto* slices = context_->storage->mutable_nestable_slices();
  size_t slice_idx = stack.back();

  PERFETTO_CHECK(cat == 0 || slices->cats()[slice_idx] == cat);
  PERFETTO_CHECK(name == 0 || slices->names()[slice_idx] == name);

  slices->set_duration(slice_idx, timestamp - slices->start_ns()[slice_idx]);

  CompleteSlice(utid);
  // TODO(primiano): auto-close B slices left open at the end.
}

void SliceTracker::CompleteSlice(UniqueTid utid) {
  threads_[utid].pop_back();
}

void SliceTracker::MaybeCloseStack(int64_t ts, SlicesStack* stack) {
  const auto& slices = context_->storage->nestable_slices();
  bool check_only = false;
  for (int i = static_cast<int>(stack->size()) - 1; i >= 0; i--) {
    size_t slice_idx = (*stack)[static_cast<size_t>(i)];

    int64_t start_ts = slices.start_ns()[slice_idx];
    int64_t dur = slices.durations()[slice_idx];
    int64_t end_ts = start_ts + dur;
    if (dur == 0) {
      check_only = true;
    }

    if (check_only) {
      PERFETTO_DCHECK(ts >= start_ts);
      PERFETTO_DCHECK(dur == 0 || ts <= end_ts);
      continue;
    }

    if (end_ts <= ts) {
      stack->pop_back();
    }
  }
}

int64_t SliceTracker::GetStackHash(const SlicesStack& stack) {
  PERFETTO_DCHECK(!stack.empty());

  const auto& slices = context_->storage->nestable_slices();

  std::string s;
  s.reserve(stack.size() * sizeof(uint64_t) * 2);
  for (size_t i = 0; i < stack.size(); i++) {
    size_t slice_idx = stack[i];
    s.append(reinterpret_cast<const char*>(&slices.cats()[slice_idx]),
             sizeof(slices.cats()[slice_idx]));
    s.append(reinterpret_cast<const char*>(&slices.names()[slice_idx]),
             sizeof(slices.names()[slice_idx]));
  }
  constexpr uint64_t kMask = uint64_t(-1) >> 1;
  return static_cast<int64_t>((std::hash<std::string>{}(s)) & kMask);
}

}  // namespace trace_processor
}  // namespace perfetto
