/*
 * Copyright (C) 2019 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 "src/trace_processor/forwarding_trace_parser.h"

#include <memory>
#include <optional>
#include <utility>

#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/status_macros.h"
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/importers/common/chunked_trace_reader.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/global_stats_tracker.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/trace_file_tracker.h"
#include "src/trace_processor/sorter/trace_sorter.h"
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/tables/metadata_tables_py.h"
#include "src/trace_processor/trace_reader_registry.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/util/clock_synchronizer.h"
#include "src/trace_processor/util/trace_type.h"

#include "protos/perfetto/common/builtin_clock.pbzero.h"

namespace perfetto::trace_processor {
namespace {

TraceSorter::SortingMode ConvertSortingMode(SortingMode sorting_mode) {
  switch (sorting_mode) {
    case SortingMode::kDefaultHeuristics:
      return TraceSorter::SortingMode::kDefault;
    case SortingMode::kForceFullSort:
      return TraceSorter::SortingMode::kFullSort;
  }
  PERFETTO_FATAL("For GCC");
}

std::optional<TraceSorter::SortingMode> GetMinimumSortingMode(
    TraceType trace_type,
    const TraceProcessorContext& context) {
  switch (trace_type) {
    case kGzipTraceType:
      return std::nullopt;

    case kAndroidDumpstateTraceType:
    case kAndroidLogcatTraceType:
    case kArtHprofTraceType:
    case kArtMethodTraceType:
    case kArtMethodV2TraceType:
    case kCollapsedStackTraceType:
    case kCtraceTraceType:
    case kFuchsiaTraceType:
    case kGeckoTraceType:
    case kInstrumentsXmlTraceType:
    case kJsonTraceType:
    case kNinjaLogTraceType:
    case kPerfDataTraceType:
    case kPerfTextTraceType:
    case kPprofTraceType:
    case kPrimesTraceType:
    case kSimpleperfProtoTraceType:
    case kSystraceTraceType:
    case kTarTraceType:
    case kUnknownTraceType:
    case kZipFile:
      return TraceSorter::SortingMode::kFullSort;

    case kProtoTraceType:
    case kSymbolsTraceType:
      return ConvertSortingMode(context.config.sorting_mode);

    case kAndroidBugreportTraceType:
      PERFETTO_FATAL(
          "This trace type should be handled at the ZipParser level");
  }
  PERFETTO_FATAL("For GCC");
}

}  // namespace

ForwardingTraceParser::ForwardingTraceParser(TraceProcessorContext* context,
                                             tables::TraceFileTable::Id id)
    : input_context_(context), file_id_(id) {}

ForwardingTraceParser::~ForwardingTraceParser() = default;

base::Status ForwardingTraceParser::Init(const TraceBlobView& blob) {
  PERFETTO_CHECK(!reader_);

  {
    auto scoped_trace =
        input_context_->global_stats_tracker->TraceExecutionTimeIntoStats(
            stats::guess_trace_type_duration_ns);
    trace_type_ = GuessTraceType(blob.data(), blob.size());
  }
  if (trace_type_ == kUnknownTraceType) {
    // If renaming this error message don't remove the "(ERR:fmt)" part.
    // The UI's error_dialog.ts uses it to make the dialog more graceful.
    return base::ErrStatus("Unknown trace type provided (ERR:fmt)");
  }
  PERFETTO_DLOG("%s trace detected", TraceTypeToString(trace_type_));

  if (file_id_.value != 0 && trace_type_ == kNinjaLogTraceType) {
    return base::ErrStatus(
        "Ninja traces currently do not support being contained inside other "
        "trace formats. Please file a bug at "
        "https://github.com/google/perfetto/issues if this is important to "
        "you.");
  }

  std::optional<TraceSorter::SortingMode> minimum_sorting_mode =
      GetMinimumSortingMode(trace_type_, *input_context_);
  if (minimum_sorting_mode) {
    input_context_->sorter->SetSortingMode(*minimum_sorting_mode);
  }
  input_context_->trace_file_tracker->StartParsing(file_id_, trace_type_);

  if (IsContainerTraceType(trace_type_)) {
    PERFETTO_DCHECK(!input_context_->trace_state);
    trace_context_ = input_context_;
  } else {
    // TODO(b/334978369) Make sure kProtoTraceType and kSystraceTraceType are
    // parsed first so that we do not get issues with
    // SetPidZeroIsUpidZeroIdleProcess()
    trace_context_ = input_context_->ForkContextForTrace(file_id_, 0);
    if (trace_type_ == kProtoTraceType || trace_type_ == kSystraceTraceType) {
      trace_context_->process_tracker->SetPidZeroIsUpidZeroIdleProcess();
    }
  }
  ASSIGN_OR_RETURN(reader_, input_context_->reader_registry->CreateTraceReader(
                                trace_type_, trace_context_));

  // Centralize clock setup for all trace formats. Proto traces add an identity
  // sync so BOOTTIME is reachable in the clock graph; the trace default clock
  // is set later by ParseClockSnapshot. All other formats know their clock
  // statically and set the global clock directly.
  using ClockId = ClockTracker::ClockId;
  if (trace_type_ == kProtoTraceType) {
    trace_context_->clock_tracker->AddDeferredIdentitySync(
        ClockId::Machine(protos::pbzero::BUILTIN_CLOCK_BOOTTIME));
  } else if (trace_type_ == kSystraceTraceType ||
             trace_type_ == kSimpleperfProtoTraceType ||
             trace_type_ == kPerfTextTraceType ||
             trace_type_ == kPerfDataTraceType ||
             trace_type_ == kArtMethodTraceType ||
             trace_type_ == kArtMethodV2TraceType) {
    trace_context_->clock_tracker->SetGlobalClock(
        ClockId::Machine(protos::pbzero::BUILTIN_CLOCK_MONOTONIC));
  } else if (trace_type_ == kFuchsiaTraceType) {
    trace_context_->clock_tracker->SetGlobalClock(
        ClockId::Machine(protos::pbzero::BUILTIN_CLOCK_BOOTTIME));
  } else if (trace_type_ == kGeckoTraceType || trace_type_ == kJsonTraceType ||
             trace_type_ == kInstrumentsXmlTraceType) {
    trace_context_->clock_tracker->SetGlobalClock(
        ClockId::TraceFile(trace_context_->trace_id().value));
  } else if (trace_type_ == kAndroidDumpstateTraceType ||
             trace_type_ == kAndroidLogcatTraceType) {
    trace_context_->clock_tracker->SetGlobalClock(
        ClockId::Machine(protos::pbzero::BUILTIN_CLOCK_REALTIME));
  }
  return base::OkStatus();
}

base::Status ForwardingTraceParser::Parse(TraceBlobView blob) {
  // If this is the first Parse() call, guess the trace type and create the
  // appropriate parser.
  if (!reader_) {
    RETURN_IF_ERROR(Init(blob));
  }
  trace_size_ += blob.size();
  return reader_->Parse(std::move(blob));
}

base::Status ForwardingTraceParser::OnPushDataToSorter() {
  if (reader_) {
    return reader_->OnPushDataToSorter();
  }
  return base::OkStatus();
}

void ForwardingTraceParser::OnEventsFullyExtracted() {
  if (reader_) {
    reader_->OnEventsFullyExtracted();
  }
  if (trace_type_ != kUnknownTraceType) {
    input_context_->trace_file_tracker->DoneParsing(file_id_, trace_size_);
  }
}

}  // namespace perfetto::trace_processor
