/*
 * Copyright (C) 2022 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/importers/proto/content_analyzer.h"

#include "perfetto/ext/base/string_utils.h"
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/proto/content_analyzer.h"
#include "src/trace_processor/storage/trace_storage.h"

#include "src/trace_processor/importers/proto/trace.descriptor.h"

namespace perfetto {
namespace trace_processor {

ProtoContentAnalyzer::ProtoContentAnalyzer(TraceProcessorContext* context)
    : context_(context),
      pool_([]() {
        DescriptorPool pool;
        base::Status status = pool.AddFromFileDescriptorSet(
            kTraceDescriptor.data(), kTraceDescriptor.size());
        if (!status.ok()) {
          PERFETTO_ELOG("Could not add TracePacket proto descriptor %s",
                        status.c_message());
        }
        return pool;
      }()),
      computer_(&pool_, ".perfetto.protos.TracePacket") {}

ProtoContentAnalyzer::~ProtoContentAnalyzer() = default;

void ProtoContentAnalyzer::ProcessPacket(
    const TraceBlobView& packet,
    const SampleAnnotation& packet_annotations) {
  auto& map = aggregated_samples_[packet_annotations];
  computer_.Reset(packet.data(), packet.length());
  for (auto sample = computer_.GetNext(); sample.has_value();
       sample = computer_.GetNext()) {
    auto* value = map.Find(computer_.GetPath());
    if (value) {
      value->size += *sample;
      ++value->count;
    } else {
      map.Insert(computer_.GetPath(), Sample{*sample, 1});
    }
  }
}

void ProtoContentAnalyzer::NotifyEndOfFile() {
  // TODO(kraskevich): consider generating a flamegraph-compatable table once
  // Perfetto UI supports custom flamegraphs (b/227644078).
  for (auto annotated_map = aggregated_samples_.GetIterator(); annotated_map;
       ++annotated_map) {
    base::FlatHashMap<util::SizeProfileComputer::FieldPath,
                      tables::ExperimentalProtoPathTable::Id,
                      util::SizeProfileComputer::FieldPathHasher>
        path_ids;
    for (auto sample = annotated_map.value().GetIterator(); sample; ++sample) {
      std::string path_string;
      std::optional<tables::ExperimentalProtoPathTable::Id> previous_path_id;
      util::SizeProfileComputer::FieldPath path;
      for (const auto& field : sample.key()) {
        if (field.has_field_name()) {
          if (!path_string.empty()) {
            path_string += '.';
          }
          path_string.append(field.field_name());
        }
        if (!path_string.empty()) {
          path_string += '.';
        }
        path_string.append(field.type_name());

        path.push_back(field);
        // Reuses existing path from |path_ids| if possible.
        {
          auto* path_id = path_ids.Find(path);
          if (path_id) {
            previous_path_id = *path_id;
            continue;
          }
        }
        // Create a new row in experimental_proto_path.
        tables::ExperimentalProtoPathTable::Row path_row;
        if (field.has_field_name()) {
          path_row.field_name = context_->storage->InternString(
              base::StringView(field.field_name()));
        }
        path_row.field_type = context_->storage->InternString(
            base::StringView(field.type_name()));
        if (previous_path_id.has_value())
          path_row.parent_id = *previous_path_id;

        auto path_id =
            context_->storage->mutable_experimental_proto_path_table()
                ->Insert(path_row)
                .id;
        if (!previous_path_id.has_value()) {
          // Add annotations to the current row as an args set.
          auto inserter = context_->args_tracker->AddArgsTo(path_id);
          for (auto& annotation : annotated_map.key()) {
            inserter.AddArg(annotation.first,
                            Variadic::String(annotation.second));
          }
        }
        previous_path_id = path_id;
        path_ids[path] = path_id;
      }

      // Add a content row referring to |previous_path_id|.
      tables::ExperimentalProtoContentTable::Row content_row;
      content_row.path =
          context_->storage->InternString(base::StringView(path_string));
      content_row.path_id = *previous_path_id;
      content_row.total_size = static_cast<int64_t>(sample.value().size);
      content_row.size = static_cast<int64_t>(sample.value().size);
      content_row.count = static_cast<int64_t>(sample.value().count);
      context_->storage->mutable_experimental_proto_content_table()->Insert(
          content_row);
    }
  }
  aggregated_samples_.Clear();
}

}  // namespace trace_processor
}  // namespace perfetto
