/*! * Copyright (c) 2023-2025 by Contributors * \file serve/event_trace_recorder.cc */ #include "event_trace_recorder.h" #include #include #include #include #include #include #include #include #include #include namespace mlc { namespace llm { namespace serve { using tvm::ffi::String; namespace detail { struct PairHash { template std::size_t operator()(const std::pair& p) const { auto h1 = std::hash{}(p.first); auto h2 = std::hash{}(p.second); return h1 ^ h2; } }; } // namespace detail /*! \brief The implementation of event trace recorder. */ class EventTraceRecorderImpl : public EventTraceRecorderObj { public: void AddEvent(const String& request_id, const std::string& event) final { double event_time = std::chrono::duration_cast>( std::chrono::system_clock::now().time_since_epoch()) .count(); { std::lock_guard lock(mutex_); AddEventInternal(request_id, event, event_time); } } void AddEvent(const Array& request_ids, const std::string& event) final { double event_time = std::chrono::duration_cast>( std::chrono::system_clock::now().time_since_epoch()) .count(); // in seconds { std::lock_guard lock(mutex_); for (const String& request_id : request_ids) { AddEventInternal(request_id, event, event_time); } } } std::string DumpJSON() final { std::unordered_map>> local_events; { std::lock_guard lock(mutex_); local_events = events_; } auto fcmp_events = [](const std::pair& lhs, const std::pair& rhs) { return lhs.first < rhs.first; }; tvm::ffi::json::Array event_array; for (const std::string& request_id : request_id_in_order_) { std::vector> event_pairs = local_events.at(request_id); std::vector> events_to_sort; events_to_sort.reserve(event_pairs.size()); for (int i = 0; i < static_cast(event_pairs.size()); ++i) { std::string event = event_pairs[i].first; double event_time = event_pairs[i].second; std::string name; std::string phase; if (event.compare(0, 6, "start ") == 0) { // Duration begin. name = event.substr(6); phase = "B"; } else if (event.compare(0, 7, "finish ") == 0) { // Duration end. name = event.substr(7); phase = "E"; } else { // Instant event. name = event; phase = "i"; } int64_t event_time_in_us = static_cast(event_time * 1e6); tvm::ffi::json::Object event_json; event_json.Set("name", name); event_json.Set("ph", phase); event_json.Set("ts", event_time_in_us); event_json.Set("pid", static_cast(1)); event_json.Set("tid", request_id); events_to_sort.push_back({event_time_in_us, event_json}); } std::sort(events_to_sort.begin(), events_to_sort.end(), fcmp_events); for (auto [timestamp, event] : events_to_sort) { event_array.push_back(std::move(event)); } } return tvm::ffi::json::Stringify(event_array); } TVM_FFI_DECLARE_OBJECT_INFO("mlc.serve.EventTraceRecorder", EventTraceRecorderImpl, EventTraceRecorderObj); private: /*! \brief The internal impl of AddEvent, taking the event time as input. */ void AddEventInternal(const std::string& request_id, const std::string& event, double event_time) { if (std::find(request_id_in_order_.begin(), request_id_in_order_.end(), request_id) == request_id_in_order_.end()) { request_id_in_order_.push_back(request_id); } int event_cnt = event_counter_[{request_id, event}]++; events_[request_id].push_back({event + " (" + std::to_string(event_cnt) + ")", event_time}); } /*! \brief The mutex ensuring only one thread can access critical regions. */ std::mutex mutex_; /************** Critical Regions **************/ /*! \brief The request ids in time order. Each id only appears once. */ std::vector request_id_in_order_; /*! \brief The number of a certain event for a request. */ std::unordered_map, int, detail::PairHash> event_counter_; /*! \brief The event list of each request together with the timestamps. */ std::unordered_map>> events_; }; EventTraceRecorder EventTraceRecorder::Create() { return EventTraceRecorder(tvm::ffi::make_object()); } TVM_FFI_STATIC_INIT_BLOCK() { namespace refl = tvm::ffi::reflection; EventTraceRecorderImpl::RegisterReflection(); refl::GlobalDef() .def("mlc.serve.EventTraceRecorder", []() { return EventTraceRecorder::Create(); }) .def("mlc.serve.EventTraceRecorderAddEvent", [](const EventTraceRecorder& trace_recorder, const String& request_id, const std::string& event) { trace_recorder->AddEvent(request_id, event); }) .def_method("mlc.serve.EventTraceRecorderDumpJSON", &EventTraceRecorderObj::DumpJSON); } } // namespace serve } // namespace llm } // namespace mlc