// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // License, and you may not use this file except in compliance with the Business Source License. // // As of the Change Date specified in that file, in accordance with // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. #include #include #include #include #include #include #include #include #include #include "query_memory_control.hpp" #include "utils/exceptions.hpp" #include "utils/logging.hpp" #include "utils/memory.hpp" #include "utils/memory_tracker.hpp" #include "utils/rw_spin_lock.hpp" #if USE_JEMALLOC #include "jemalloc/jemalloc.h" #endif namespace memgraph::memory { #if USE_JEMALLOC void QueriesMemoryControl::UpdateThreadToTransactionId(const std::thread::id &thread_id, uint64_t transaction_id) { auto accessor = thread_id_to_transaction_id.access(); auto elem = accessor.find(thread_id); if (elem == accessor.end()) { accessor.insert({thread_id, {transaction_id, 1}}); } else { elem->transaction_id.cnt++; } } void QueriesMemoryControl::EraseThreadToTransactionId(const std::thread::id &thread_id, uint64_t transaction_id) { auto accessor = thread_id_to_transaction_id.access(); auto elem = accessor.find(thread_id); MG_ASSERT(elem != accessor.end() && elem->transaction_id == transaction_id); elem->transaction_id.cnt--; if (elem->transaction_id.cnt == 0) { accessor.remove(thread_id); } } void QueriesMemoryControl::TrackAllocOnCurrentThread(size_t size) { auto thread_id_to_transaction_id_accessor = thread_id_to_transaction_id.access(); // we might be just constructing mapping between thread id and transaction id // so we miss this allocation auto thread_id_to_transaction_id_elem = thread_id_to_transaction_id_accessor.find(std::this_thread::get_id()); if (thread_id_to_transaction_id_elem == thread_id_to_transaction_id_accessor.end()) { return; } auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access(); auto transaction_id_to_tracker = transaction_id_to_tracker_accessor.find(thread_id_to_transaction_id_elem->transaction_id); // It can happen that some allocation happens between mapping thread to // transaction id, so we miss this allocation if (transaction_id_to_tracker == transaction_id_to_tracker_accessor.end()) [[unlikely]] { return; } auto &query_tracker = transaction_id_to_tracker->tracker; query_tracker.TrackAlloc(size); } void QueriesMemoryControl::TrackFreeOnCurrentThread(size_t size) { auto thread_id_to_transaction_id_accessor = thread_id_to_transaction_id.access(); // we might be just constructing mapping between thread id and transaction id // so we miss this allocation auto thread_id_to_transaction_id_elem = thread_id_to_transaction_id_accessor.find(std::this_thread::get_id()); if (thread_id_to_transaction_id_elem == thread_id_to_transaction_id_accessor.end()) { return; } auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access(); auto transaction_id_to_tracker = transaction_id_to_tracker_accessor.find(thread_id_to_transaction_id_elem->transaction_id); // It can happen that some allocation happens between mapping thread to // transaction id, so we miss this allocation if (transaction_id_to_tracker == transaction_id_to_tracker_accessor.end()) [[unlikely]] { return; } auto &query_tracker = transaction_id_to_tracker->tracker; query_tracker.TrackFree(size); } void QueriesMemoryControl::CreateTransactionIdTracker(uint64_t transaction_id, size_t inital_limit) { auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access(); auto [elem, result] = transaction_id_to_tracker_accessor.insert({transaction_id, utils::QueryMemoryTracker{}}); elem->tracker.SetQueryLimit(inital_limit); } bool QueriesMemoryControl::EraseTransactionIdTracker(uint64_t transaction_id) { auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access(); auto removed = transaction_id_to_tracker.access().remove(transaction_id); return removed; } bool QueriesMemoryControl::CheckTransactionIdTrackerExists(uint64_t transaction_id) { auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access(); return transaction_id_to_tracker_accessor.contains(transaction_id); } void QueriesMemoryControl::TryCreateTransactionProcTracker(uint64_t transaction_id, int64_t procedure_id, size_t limit) { auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access(); auto query_tracker = transaction_id_to_tracker_accessor.find(transaction_id); if (query_tracker == transaction_id_to_tracker_accessor.end()) { return; } query_tracker->tracker.TryCreateProcTracker(procedure_id, limit); } void QueriesMemoryControl::SetActiveProcIdTracker(uint64_t transaction_id, int64_t procedure_id) { auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access(); auto query_tracker = transaction_id_to_tracker_accessor.find(transaction_id); if (query_tracker == transaction_id_to_tracker_accessor.end()) { return; } query_tracker->tracker.SetActiveProc(procedure_id); } void QueriesMemoryControl::PauseProcedureTracking(uint64_t transaction_id) { auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access(); auto query_tracker = transaction_id_to_tracker_accessor.find(transaction_id); if (query_tracker == transaction_id_to_tracker_accessor.end()) { return; } query_tracker->tracker.StopProcTracking(); } inline int &Get_Thread_Tracker() { // store variable in bss segment for each thread // https://cs-fundamentals.com/c-programming/memory-layout-of-c-program-code-data-segments#size-of-code-data-bss-segments static thread_local int is_thread_tracked{0}; return is_thread_tracked; } bool QueriesMemoryControl::IsThreadTracked() { return Get_Thread_Tracker() == 1; } #endif void StartTrackingCurrentThreadTransaction(uint64_t transaction_id) { #if USE_JEMALLOC Get_Thread_Tracker() = 0; GetQueriesMemoryControl().UpdateThreadToTransactionId(std::this_thread::get_id(), transaction_id); Get_Thread_Tracker() = 1; #endif } void StopTrackingCurrentThreadTransaction(uint64_t transaction_id) { #if USE_JEMALLOC Get_Thread_Tracker() = 0; GetQueriesMemoryControl().EraseThreadToTransactionId(std::this_thread::get_id(), transaction_id); #endif } void TryStartTrackingOnTransaction(uint64_t transaction_id, size_t limit) { #if USE_JEMALLOC if (GetQueriesMemoryControl().CheckTransactionIdTrackerExists(transaction_id)) { return; } GetQueriesMemoryControl().CreateTransactionIdTracker(transaction_id, limit); #endif } void TryStopTrackingOnTransaction(uint64_t transaction_id) { #if USE_JEMALLOC if (!GetQueriesMemoryControl().CheckTransactionIdTrackerExists(transaction_id)) { return; } GetQueriesMemoryControl().EraseTransactionIdTracker(transaction_id); #endif } #if USE_JEMALLOC bool IsTransactionTracked(uint64_t transaction_id) { return GetQueriesMemoryControl().CheckTransactionIdTrackerExists(transaction_id); } #else bool IsTransactionTracked(uint64_t /*transaction_id*/) { return false; } #endif void CreateOrContinueProcedureTracking(uint64_t transaction_id, int64_t procedure_id, size_t limit) { #if USE_JEMALLOC if (!GetQueriesMemoryControl().CheckTransactionIdTrackerExists(transaction_id)) { LOG_FATAL("Memory tracker for transaction was not set"); } GetQueriesMemoryControl().TryCreateTransactionProcTracker(transaction_id, procedure_id, limit); GetQueriesMemoryControl().SetActiveProcIdTracker(transaction_id, procedure_id); #endif } void PauseProcedureTracking(uint64_t transaction_id) { #if USE_JEMALLOC GetQueriesMemoryControl().PauseProcedureTracking(transaction_id); #endif } } // namespace memgraph::memory