/*
 * Copyright (C) 2020 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_PROTO_ASYNC_TRACK_SET_TRACKER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ASYNC_TRACK_SET_TRACKER_H_

#include "src/trace_processor/storage/trace_storage.h"

namespace perfetto {
namespace trace_processor {

class TraceProcessorContext;
class AsyncTrackSetTrackerUnittest;

// Tracker used to reduce the number of trace processor tracks corresponding
// to a single "UI track".
//
// UIs using trace processor want to display all slices in the same context
// (e.g. same upid) and same name into a single track. However, because trace
// processor does not allow parallel slices on a single track (because it breaks
// things like span join, self time computation etc.), at the trace processor
// level these parallel slices are put on different tracks.
//
// Creating a new track for every event, however, leads to an explosion of
// tracks which is undesirable. This class exists to multiplex slices so that
// n events correspond to a single track in a way which minimises the number of
// tracks which needs to be merged by the UI.
//
// The intended usage of this class is for callers to first call one of the
// Intern* methods to obtain a TrackSetId followed by Begin/End just before
// calling into SliceTracker's Begin/End respectively. For example:
//  TrackSetId set_id = track_set_tracker->InternProcessTrackSet(upid, name);
//  if (event.begin) {
//    TrackId id = track_set_tracker->Begin(set_id, cookie);
//    slice_tracker->Begin(ts, id, ...)
//  } else {
//    ... (same thing with end)
//  }
// Alternatively, instead of Begin/End, Scoped can also be called if supported
// by the track type.
class AsyncTrackSetTracker {
 public:
  using TrackSetId = uint32_t;

  explicit AsyncTrackSetTracker(TraceProcessorContext* context);
  ~AsyncTrackSetTracker() = default;

  // Interns a set of global async slice tracks associated with the given name.
  TrackSetId InternGlobalTrackSet(StringId name);

  // Interns a set of process async slice tracks associated with the given name
  // and upid.
  TrackSetId InternProcessTrackSet(UniquePid, StringId name);

  // Interns a set of Android legacy unnesteable async slice tracks
  // associated with the given upid and name.
  // Scoped is *not* supported for this track set type.
  TrackSetId InternAndroidLegacyUnnestableTrackSet(UniquePid, StringId name);

  // Starts a new slice on the given async track set which has the given cookie.
  TrackId Begin(TrackSetId id, int64_t cookie);

  // Ends a new slice on the given async track set which has the given cookie.
  TrackId End(TrackSetId id, int64_t cookie);

  // Creates a scoped slice on the given async track set.
  // This method makes sure that any other slice in this track set does
  // not happen simultaneously on the returned track.
  // Only supported on selected track set types; read the documentation for
  // the Intern* method for your track type to check if supported.
  TrackId Scoped(TrackSetId id, int64_t ts, int64_t dur);

 private:
  friend class AsyncTrackSetTrackerUnittest;

  struct ProcessTuple {
    UniquePid upid;
    StringId name;

    friend bool operator<(const ProcessTuple& l, const ProcessTuple& r) {
      return std::tie(l.upid, l.name) < std::tie(r.upid, r.name);
    }
  };

  // Indicates the nesting behaviour of slices associated to a single slice
  // stack.
  enum class NestingBehaviour {
    // Indicates that slices are unnestable; that is, it is an error
    // to call Begin -> Begin with a single cookie without End inbetween.
    // This pattern should be the default behaviour that most async slices
    // should use.
    kUnnestable,

    // Indicates that slices are unnestable but also saturating; that is
    // calling Begin -> Begin only causes a single Begin to be recorded.
    // This is only really useful for Android async slices which have this
    // behaviour for legacy reasons. See the comment in
    // SystraceParser::ParseSystracePoint for information on why
    // this behaviour exists.
    kLegacySaturatingUnnestable,
  };

  enum class TrackSetType {
    kGlobal,
    kProcess,
    kAndroidLegacyUnnestable,
  };

  struct TrackState {
    TrackId id;

    enum class SliceType { kCookie, kTimestamp };
    SliceType slice_type;

    union {
      // Only valid for |slice_type| == |SliceType::kCookie|.
      int64_t cookie;

      // Only valid for |slice_type| == |SliceType::kTimestamp|.
      int64_t ts_end;
    };

    // Only used for |slice_type| == |SliceType::kCookie|.
    uint32_t nest_count;
  };

  struct TrackSet {
    TrackSetType type;
    union {
      // Only set when |type| == |TrackSetType::kGlobal|.
      StringId global_track_name;
      // Only set when |type| == |TrackSetType::kFrameTimeline| or
      // |TrackSetType::kAndroidLegacyUnnestable|.
      ProcessTuple process_tuple;
    };
    NestingBehaviour nesting_behaviour;
    std::vector<TrackState> tracks;
  };

  TrackSetId CreateUnnestableTrackSetForTesting(UniquePid upid, StringId name) {
    AsyncTrackSetTracker::TrackSet set;
    set.process_tuple = ProcessTuple{upid, name};
    set.type = AsyncTrackSetTracker::TrackSetType::kAndroidLegacyUnnestable;
    set.nesting_behaviour = NestingBehaviour::kUnnestable;
    track_sets_.emplace_back(set);
    return static_cast<TrackSetId>(track_sets_.size() - 1);
  }

  // Returns the state for a track using the following algorithm:
  // 1. If a track exists with the given cookie in the track set, returns
  //    that track.
  // 2. Otherwise, looks for any track in the set which is "open" (i.e.
  //    does not have another slice currently scheduled).
  // 3. Otherwise, creates a new track and associates it with the set.
  TrackState& GetOrCreateTrackForCookie(TrackSet& set, int64_t cookie);

  TrackId CreateTrackForSet(const TrackSet& set);

  std::map<StringId, TrackSetId> global_track_set_ids_;
  std::map<ProcessTuple, TrackSetId> process_track_set_ids_;
  std::map<ProcessTuple, TrackSetId> android_legacy_unnestable_track_set_ids_;
  std::vector<TrackSet> track_sets_;

  const StringId android_source_ = kNullStringId;

  TraceProcessorContext* const context_;
};

}  // namespace trace_processor
}  // namespace perfetto

#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ASYNC_TRACK_SET_TRACKER_H_
