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

#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_SYSTRACE_SYSTRACE_PARSER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_SYSTRACE_SYSTRACE_PARSER_H_

#include <ostream>

#include "src/trace_processor/types/trace_processor_context.h"

#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
#include "src/trace_processor/storage/trace_storage.h"

namespace perfetto {
namespace trace_processor {

namespace systrace_utils {

// Visible for unittesting.
enum class SystraceParseResult { kFailure = 0, kUnsupported, kSuccess };

// Visible for unittesting.
struct SystraceTracePoint {
  SystraceTracePoint() {}

  static SystraceTracePoint B(uint32_t tgid, base::StringView name) {
    return SystraceTracePoint('B', tgid, std::move(name), 0, {});
  }

  static SystraceTracePoint E(uint32_t tgid) {
    return SystraceTracePoint('E', tgid, {}, 0, {});
  }

  static SystraceTracePoint C(uint32_t tgid,
                              base::StringView name,
                              int64_t value) {
    return SystraceTracePoint('C', tgid, std::move(name), value, {});
  }

  static SystraceTracePoint S(uint32_t tgid,
                              base::StringView name,
                              int64_t cookie) {
    return SystraceTracePoint('S', tgid, std::move(name), cookie, {});
  }

  static SystraceTracePoint F(uint32_t tgid,
                              base::StringView name,
                              int64_t cookie) {
    return SystraceTracePoint('F', tgid, std::move(name), cookie, {});
  }

  static SystraceTracePoint I(uint32_t tgid, base::StringView name) {
    return SystraceTracePoint('I', tgid, std::move(name), 0, {});
  }

  static SystraceTracePoint N(uint32_t tgid,
                              base::StringView track_name,
                              base::StringView name) {
    return SystraceTracePoint('N', tgid, std::move(name), 0,
                              std::move(track_name));
  }

  static SystraceTracePoint G(uint32_t tgid,
                              base::StringView track_name,
                              base::StringView name,
                              int64_t cookie) {
    return SystraceTracePoint('G', tgid, std::move(name), cookie,
                              std::move(track_name));
  }

  static SystraceTracePoint H(uint32_t tgid,
                              base::StringView track_name,
                              int64_t cookie) {
    return SystraceTracePoint('H', tgid, {}, cookie, std::move(track_name));
  }

  SystraceTracePoint(char p,
                     uint32_t tg,
                     base::StringView n,
                     int64_t v,
                     base::StringView s)
      : phase(p), tgid(tg), name(std::move(n)), int_value(v), str_value(s) {}

  // Phase can be one of B, E, C, S, F, I, N, G, H.
  char phase = '\0';

  uint32_t tgid = 0;

  // For phase = B, C, S, F, N, U, G.
  base::StringView name;

  // For phase = C (counter value) and B, S, F, N, G, H (async cookie).
  int64_t int_value = 0;

  // For phase = N, G, H (track name)
  base::StringView str_value;

  // Visible for unittesting.
  friend std::ostream& operator<<(std::ostream& os,
                                  const SystraceTracePoint& point) {
    return os << "SystraceTracePoint{'" << point.phase << "', " << point.tgid
              << ", \"" << point.name.ToStdString() << "\", " << point.int_value
              << ", \"" << point.str_value.ToStdString() << "\""
              << "}";
  }
};

// We have to handle trace_marker events of a few different types:
// 1.   some random text
// 2.   B|1636|pokeUserActivity
// 3.   E|1636
// 4.   C|1636|wq:monitor|0
// 5.   S|1636|frame_capture|123
// 6.   F|1636|frame_capture|456
// 7.   C|3209|TransfersBytesPendingOnDisk-value|0|Blob
// 8.   I|4820|instant
// 9.   N|1938|track_name|instant_name
// 10.  G|1339|track_name|slice_name|789
// 11.  H|6890|track_name|slice_name|135
// 12.  H|6890|track_name|135
// Counters emitted by chromium can have a further "category group" appended
// ("Blob" in the example below). We ignore the category group.
inline SystraceParseResult ParseSystraceTracePoint(
    base::StringView str_untrimmed,
    SystraceTracePoint* out) {
  *out = {};

  // Strip trailing \n and \0. StringViews are not null-terminated, but the
  // writer could have appended a stray \0 depending on where the trace comes
  // from.
  size_t len = str_untrimmed.size();
  for (; len > 0; --len) {
    char last_char = str_untrimmed.at(len - 1);
    if (last_char != '\n' && last_char != '\0')
      break;
  }
  base::StringView str = str_untrimmed.substr(0, len);

  size_t off = 0;

  // This function reads the next field up to the next '|', '\0' or end(). It
  // advances |off| as it goes through fields.
  auto read_next_field = [&off, &str, len]() {
    for (size_t field_start = off;; ++off) {
      char c = off >= len ? '\0' : str.at(off);
      if (c == '|' || c == '\0') {
        auto res = str.substr(field_start, off - field_start);
        ++off;  // Eat the separator.
        return res;
      }
    }
  };

  auto f0_phase = read_next_field();
  if (PERFETTO_UNLIKELY(f0_phase.empty()))
    return SystraceParseResult::kFailure;
  out->phase = f0_phase.at(0);

  auto f1_tgid = read_next_field();
  auto opt_tgid = base::StringToUInt32(f1_tgid.ToStdString());
  out->tgid = opt_tgid.value_or(0);
  const bool has_tgid = opt_tgid.has_value();

  switch (out->phase) {
    case 'B': {  // Begin thread-scoped synchronous slice.
      if (!has_tgid)
        return SystraceParseResult::kFailure;
      auto f2_name = str.substr(off);  // It's fine even if |off| >= end().
      if (f2_name.empty()) {
        out->name = base::StringView("[empty slice name]");
      } else {
        out->name = f2_name;
      }
      return SystraceParseResult::kSuccess;
    }
    case 'E':  // End thread-scoped synchronous slice.
      // Some non-Android traces (Flutter) use just "E" (aosp/1244409). Allow
      // empty TGID on end slices. By design they are thread-scoped anyways.
      return SystraceParseResult::kSuccess;
    case 'S':    // Begin of async slice.
    case 'F': {  // End of async slice.
      auto f2_name = read_next_field();
      auto f3_cookie = read_next_field();
      auto maybe_cookie = base::StringToInt64(f3_cookie.ToStdString());
      if (PERFETTO_UNLIKELY(!has_tgid || f2_name.empty() || f3_cookie.empty() ||
                            !maybe_cookie)) {
        return SystraceParseResult::kFailure;
      }
      out->name = f2_name;
      out->int_value = *maybe_cookie;
      return SystraceParseResult::kSuccess;
    }
    case 'I': {  // Instant.
      auto f2_name = read_next_field();
      if (PERFETTO_UNLIKELY(!has_tgid || f2_name.empty())) {
        return SystraceParseResult::kFailure;
      }
      out->name = f2_name;
      return SystraceParseResult::kSuccess;
    }
    case 'N': {  // Instant on track.
      auto f2_track_name = read_next_field();
      auto f3_name = read_next_field();
      if (PERFETTO_UNLIKELY(!has_tgid || f2_track_name.empty() ||
                            f3_name.empty())) {
        return SystraceParseResult::kFailure;
      }
      out->name = f3_name;
      out->str_value = f2_track_name;
      return SystraceParseResult::kSuccess;
    }
    case 'C': {  // Counter.
      auto f2_name = read_next_field();
      auto f3_value = read_next_field();
      auto maybe_value = base::StringToInt64(f3_value.ToStdString());
      if (PERFETTO_UNLIKELY(!has_tgid || f2_name.empty() || f3_value.empty() ||
                            !maybe_value)) {
        return SystraceParseResult::kFailure;
      }
      out->name = f2_name;
      out->int_value = *maybe_value;
      return SystraceParseResult::kSuccess;
    }
    case 'G': {  // Begin of async slice on track.
      auto f2_track_name = read_next_field();
      auto f3_name = read_next_field();
      auto maybe_cookie = base::StringToInt64(read_next_field().ToStdString());
      if (PERFETTO_UNLIKELY(!has_tgid || f2_track_name.empty() ||
                            f3_name.empty() || !maybe_cookie)) {
        return SystraceParseResult::kFailure;
      }
      out->name = f3_name;
      out->str_value = f2_track_name;
      out->int_value = *maybe_cookie;
      return SystraceParseResult::kSuccess;
    }
    case 'H': {  // End of async slice on track.
      auto f2_track_name = read_next_field();
      auto f3 = read_next_field();
      auto f4 = read_next_field();
      auto maybe_cookie_str = f4.empty() ? f3 : f4;
      auto maybe_cookie = base::StringToInt64(maybe_cookie_str.ToStdString());
      if (PERFETTO_UNLIKELY(!has_tgid || f2_track_name.empty() ||
                            !maybe_cookie)) {
        return SystraceParseResult::kFailure;
      }
      out->str_value = f2_track_name;
      out->int_value = *maybe_cookie;
      return SystraceParseResult::kSuccess;
    }
    default:
      if (str.find("trace_event_clock_sync:") == 0)
        return SystraceParseResult::kUnsupported;
      return SystraceParseResult::kFailure;
  }
}

// Visible for unittesting.
inline bool operator==(const SystraceTracePoint& x,
                       const SystraceTracePoint& y) {
  return std::tie(x.phase, x.tgid, x.name, x.int_value, x.str_value) ==
         std::tie(y.phase, y.tgid, y.name, y.int_value, y.str_value);
}

}  // namespace systrace_utils

class SystraceParser : public Destructible {
 public:
  static SystraceParser* GetOrCreate(TraceProcessorContext* context) {
    if (!context->systrace_parser) {
      context->systrace_parser.reset(new SystraceParser(context));
    }
    return static_cast<SystraceParser*>(context->systrace_parser.get());
  }
  ~SystraceParser() override;

  void ParsePrintEvent(int64_t ts, uint32_t pid, base::StringView event);

  // Parse a kernel event that mimics the systrace format.
  void ParseKernelTracingMarkWrite(int64_t ts,
                                   uint32_t pid,
                                   char trace_type,
                                   bool trace_begin,
                                   base::StringView trace_name,
                                   uint32_t tgid,
                                   int64_t value);

  // Parse a kernel "systrace/0" event which mimics the systrace format.
  void ParseZeroEvent(int64_t ts,
                      uint32_t pid,
                      int32_t flag,
                      base::StringView name,
                      uint32_t tgid,
                      int64_t value);

 private:
  explicit SystraceParser(TraceProcessorContext*);
  void ParseSystracePoint(int64_t ts,
                          uint32_t pid,
                          systrace_utils::SystraceTracePoint event);
  void PostProcessSpecialSliceBegin(int64_t ts, base::StringView name);

  TraceProcessorContext* const context_;
  const StringId lmk_id_;
  const StringId oom_score_adj_id_;
  const StringId screen_state_id_;
  const StringId cookie_id_;
};

}  // namespace trace_processor
}  // namespace perfetto

#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_SYSTRACE_SYSTRACE_PARSER_H_
