carb/profiler/ProfilerUtils.h

File members: carb/profiler/ProfilerUtils.h

// Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//

#pragma once

#include "IProfiler.h"
#include "../cpp/Atomic.h"
#include "../settings/ISettings.h"
#include "../InterfaceUtils.h"

namespace carb
{
namespace profiler
{

namespace detail
{

struct String2
{
    carb::profiler::StaticStringType first;
    carb::profiler::StaticStringType second;

    constexpr String2(StaticStringType first, StaticStringType second) noexcept : first(first), second(second)
    {
    }
};

constexpr String2 makeString2(StaticStringType first, StaticStringType second) noexcept
{
    return String2{ first, second };
}

struct String3
{
    carb::profiler::StaticStringType first;
    carb::profiler::StaticStringType second;
    carb::profiler::StaticStringType third;

    constexpr String3(StaticStringType first, StaticStringType second, StaticStringType third) noexcept
        : first(first), second(second), third(third)
    {
    }
};

constexpr String3 makeString3(StaticStringType first, StaticStringType second, StaticStringType third) noexcept
{
    return String3{ first, second, third };
}

} // namespace detail

class Channel final
{
    uint64_t m_mask;
    bool m_enabled;
    const char* m_name;
    Channel* m_next;

    struct ModuleData
    {
        Channel* head{ nullptr };
        LoadHookHandle onSettingsLoadHandle{ kInvalidLoadHook };
        dictionary::SubscriptionId* changeSubscription{ nullptr };

#if CARB_ASSERT_ENABLED
        ~ModuleData()
        {
            // If these weren't unregistered we could crash later
            CARB_ASSERT(onSettingsLoadHandle == kInvalidLoadHook);
            CARB_ASSERT(changeSubscription == nullptr);
        }
#endif
    };

    static ModuleData& moduleData()
    {
        static ModuleData s_moduleData;
        return s_moduleData;
    }

    static void onSettingsLoad(const PluginDesc&, void*)
    {
        // DO NOT USE getCachedInterface here! This is called by a load hook, which can be triggered by
        // getCachedInterface in this module. This means if we were to recursively call getCachedInterface() here, we
        // could hang indefinitely as this thread is the thread responsible for loading the cached interface.
        if (loadSettings(getFramework()->tryAcquireInterface<settings::ISettings>(), true))
        {
            g_carbFramework->removeLoadHook(moduleData().onSettingsLoadHandle);
            moduleData().onSettingsLoadHandle = kInvalidLoadHook;
        }
    }

    static void onSettingsUnload(void*, void*)
    {
        // Settings was unloaded. Make sure we no longer have a subscription callback.
        moduleData().changeSubscription = nullptr;
    }

    static void onSettingsChange(const dictionary::Item*,
                                 const dictionary::Item* changedItem,
                                 dictionary::ChangeEventType eventType,
                                 void*)
    {
        if (eventType == dictionary::ChangeEventType::eDestroyed)
            return;

        auto dict = getCachedInterface<dictionary::IDictionary>();

        // Only care about elements that can change at runtime.
        const char* name = dict->getItemName(changedItem);
        if (strcmp(name, "enabled") != 0 && strcmp(name, "mask") != 0)
            return;

        loadSettings(
            getCachedInterface<settings::ISettings>(), false, dict->getItemName(dict->getItemParent(changedItem)));
    }

    static bool loadSettings(settings::ISettings* settings, bool initial, const char* channelName = nullptr)
    {
        // Only proceed if settings is already initialized
        if (!settings)
            return false;

        auto dict = carb::getCachedInterface<dictionary::IDictionary>();
        if (!dict)
            return false;

        auto root = settings->getSettingsDictionary("/profiler/channels");
        if (root)
        {
            for (Channel* c = moduleData().head; c; c = c->m_next)
            {
                if (channelName && strcmp(c->m_name, channelName) != 0)
                    continue;

                auto channelRoot = dict->getItem(root, c->m_name);
                if (!channelRoot)
                    continue;

                auto enabled = dict->getItem(channelRoot, "enabled");
                if (enabled)
                {
                    c->setEnabled(dict->getAsBool(enabled));
                }
                auto mask = dict->getItem(channelRoot, "mask");
                if (mask)
                {
                    c->setMask(uint64_t(dict->getAsInt64(mask)));
                }
            }
        }

        // Register a change subscription on initial setup if we have any channels.
        if (initial && !moduleData().changeSubscription && moduleData().head)
        {
            moduleData().changeSubscription =
                settings->subscribeToTreeChangeEvents("/profiler/channels", onSettingsChange, nullptr);

            ::g_carbFramework->addReleaseHook(settings, onSettingsUnload, nullptr);
        }
        return true;
    }

public:
    Channel(uint64_t mask, bool enabled, const char* name) : m_mask(mask), m_enabled(enabled), m_name(name)
    {
        // Add ourselves to the list of channels for this module
        auto& head = moduleData().head;
        m_next = head;
        head = this;
    }

    const char* getName() const noexcept
    {
        return m_name;
    }

    uint64_t getMask() const noexcept
    {
        return m_mask;
    }

    void setMask(uint64_t mask) noexcept
    {
        cpp::atomic_ref<uint64_t>(m_mask).store(mask, std::memory_order_release);
    }

    bool isEnabled() const noexcept
    {
        return m_enabled;
    }

    void setEnabled(bool enabled) noexcept
    {
        cpp::atomic_ref<bool>(m_enabled).store(enabled, std::memory_order_release);
    }

    static void onProfilerRegistered()
    {
        // example-begin acquire-without-init
        // Don't try to load settings, but if it's already available we will load settings from it.
        auto settings = g_carbFramework->tryAcquireExistingInterface<settings::ISettings>();
        // example-end acquire-without-init
        if (!loadSettings(settings, true))
        {
            // If settings isn't available, wait for it to load.
            moduleData().onSettingsLoadHandle =
                g_carbFramework->addLoadHook<settings::ISettings>(nullptr, onSettingsLoad, nullptr);
        }
    }

    static void onProfilerUnregistered()
    {
        if (moduleData().onSettingsLoadHandle != kInvalidLoadHook)
        {
            g_carbFramework->removeLoadHook(moduleData().onSettingsLoadHandle);
            moduleData().onSettingsLoadHandle = kInvalidLoadHook;
        }
        if (moduleData().changeSubscription)
        {
            // Don't re-initialize settings if it's already been unloaded (though in this case we should've gotten a
            // callback)
            auto settings = g_carbFramework->tryAcquireExistingInterface<settings::ISettings>();
            CARB_ASSERT(settings);
            if (settings)
            {
                settings->unsubscribeToChangeEvents(moduleData().changeSubscription);
                g_carbFramework->removeReleaseHook(settings, onSettingsUnload, nullptr);
            }
            moduleData().changeSubscription = nullptr;
        }
    }
};

class ProfileZoneStatic final
{
    const uint64_t m_mask;
    ZoneId m_zoneId;

public:
    ProfileZoneStatic(const uint64_t mask, const ::carb::profiler::detail::String3& tup, int line) : m_mask(mask)
    {
        auto profiler = g_carbProfiler.load(std::memory_order_acquire);
        if (profiler && ((mask ? mask : kCaptureMaskDefault) & g_carbProfilerMask.load(std::memory_order_relaxed)))
            m_zoneId = profiler->beginStatic(m_mask, tup.first, tup.second, line, tup.third);
        else
            m_zoneId = kNoZoneId;
    }

    ProfileZoneStatic(const Channel& channel, const ::carb::profiler::detail::String3& tup, int line)
        : m_mask(channel.getMask())
    {
        auto profiler = g_carbProfiler.load(std::memory_order_acquire);
        if (profiler && channel.isEnabled())
            m_zoneId = profiler->beginStatic(m_mask, tup.first, tup.second, line, tup.third);
        else
            m_zoneId = kNoZoneId;
    }

    ~ProfileZoneStatic()
    {
        auto profiler = g_carbProfiler.load(std::memory_order_acquire);
        if (profiler && m_zoneId != kNoZoneId)
            profiler->endEx(m_mask, m_zoneId);
    }
};

class ProfileZoneDynamic final
{
    const uint64_t m_mask;
    ZoneId m_zoneId;

public:
    template <typename... Args>
    ProfileZoneDynamic(
        const uint64_t mask, const ::carb::profiler::detail::String2& tup, int line, const char* nameFmt, Args&&... args)
        : m_mask(mask)
    {
        auto profiler = g_carbProfiler.load(std::memory_order_acquire);
        if (profiler && ((mask ? mask : kCaptureMaskDefault) & g_carbProfilerMask.load(std::memory_order_relaxed)))
            m_zoneId = profiler->beginDynamic(m_mask, tup.first, tup.second, line, nameFmt, std::forward<Args>(args)...);
        else
            m_zoneId = kNoZoneId;
    }

    template <typename... Args>
    ProfileZoneDynamic(const Channel& channel,
                       const ::carb::profiler::detail::String2& tup,
                       int line,
                       const char* nameFmt,
                       Args&&... args)
        : m_mask(channel.getMask())
    {
        auto profiler = g_carbProfiler.load(std::memory_order_acquire);
        if (profiler && channel.isEnabled())
            m_zoneId = profiler->beginDynamic(m_mask, tup.first, tup.second, line, nameFmt, std::forward<Args>(args)...);
        else
            m_zoneId = kNoZoneId;
    }

    ~ProfileZoneDynamic()
    {
        auto profiler = g_carbProfiler.load(std::memory_order_acquire);
        if (profiler && m_zoneId != kNoZoneId)
            profiler->endEx(m_mask, m_zoneId);
    }
};

} // namespace profiler
} // namespace carb