// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in // the documentation and/or other materials provided with the // distribution. // * Neither the name of Google, Inc. nor the names of its contributors // may be used to endorse or promote products derived from this // software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE // COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS // OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. #ifndef TOOLS_PLATFORM_H_ #define TOOLS_PLATFORM_H_ #include "PlatformMacros.h" #include "json/json.h" #include "mozilla/Atomics.h" #include "mozilla/BaseProfilerDetail.h" #include "mozilla/Logging.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/ProfileBufferEntrySerialization.h" #include "mozilla/ProfileJSONWriter.h" #include "mozilla/ProfilerUtils.h" #include "mozilla/ProgressLogger.h" #include "mozilla/TimeStamp.h" #include "mozilla/UniquePtr.h" #include "mozilla/Vector.h" #include "nsString.h" #include "shared-libraries.h" #include #include #include class ProfilerCodeAddressService; namespace mozilla { struct SymbolTable; } extern mozilla::LazyLogModule gProfilerLog; // These are for MOZ_LOG="prof:3" or higher. It's the default logging level for // the profiler, and should be used sparingly. #define LOG_TEST MOZ_LOG_TEST(gProfilerLog, mozilla::LogLevel::Info) #define LOG(arg, ...) \ MOZ_LOG(gProfilerLog, mozilla::LogLevel::Info, \ ("[%" PRIu64 "] " arg, \ uint64_t(profiler_current_process_id().ToNumber()), ##__VA_ARGS__)) // These are for MOZ_LOG="prof:4" or higher. It should be used for logging that // is somewhat more verbose than LOG. #define DEBUG_LOG_TEST MOZ_LOG_TEST(gProfilerLog, mozilla::LogLevel::Debug) #define DEBUG_LOG(arg, ...) \ MOZ_LOG(gProfilerLog, mozilla::LogLevel::Debug, \ ("[%" PRIu64 "] " arg, \ uint64_t(profiler_current_process_id().ToNumber()), ##__VA_ARGS__)) typedef uint8_t* Address; // Stringify the given JSON value, in the most compact format. // Note: Numbers are limited to a precision of 6 decimal digits, so that // timestamps in ms have a precision in ns. Json::String ToCompactString(const Json::Value& aJsonValue); // Profiling log stored in a Json::Value. The actual log only exists while the // profiler is running, and will be inserted at the end of the JSON profile. class ProfilingLog { public: // These will be called by ActivePS when the profiler starts/stops. static void Init(); static void Destroy(); // Access the profiling log JSON object, in order to modify it. // Only calls the given function if the profiler is active. // Thread-safe. But `aF` must not call other locking profiler functions. // This is intended to capture some internal logging that doesn't belong in // other places like markers. The log is accessible through the JS console on // profiler.firefox.com, in the `profile.profilingLog` object; the data format // is intentionally not defined, and not intended to be shown in the // front-end. // Please use caution not to output too much data. template static void Access(F&& aF) { mozilla::baseprofiler::detail::BaseProfilerAutoLock lock{gMutex}; if (gLog) { std::forward(aF)(*gLog); } } #define DURATION_JSON_SUFFIX "_ms" // Convert a TimeDuration to the value to be stored in the log. // Use DURATION_JSON_SUFFIX as suffix in the property name. static Json::Value Duration(const mozilla::TimeDuration& aDuration) { return Json::Value{aDuration.ToMilliseconds()}; } #define TIMESTAMP_JSON_SUFFIX "_TSms" // Convert a TimeStamp to the value to be stored in the log. // Use TIMESTAMP_JSON_SUFFIX as suffix in the property name. static Json::Value Timestamp( const mozilla::TimeStamp& aTimestamp = mozilla::TimeStamp::Now()) { if (aTimestamp.IsNull()) { return Json::Value{0.0}; } return Duration(aTimestamp - mozilla::TimeStamp::ProcessCreation()); } static bool IsLockedOnCurrentThread(); private: static mozilla::baseprofiler::detail::BaseProfilerMutex gMutex; static mozilla::UniquePtr gLog; }; // ---------------------------------------------------------------------------- // Miscellaneous // If positive, skip stack-sampling in the sampler thread loop. // Users should increment it atomically when samplings should be avoided, and // later decrement it back. Multiple uses can overlap. // There could be a sampling in progress when this is first incremented, so if // it is critical to prevent any sampling, lock the profiler mutex instead. // Relaxed ordering, because it's used to request that the profiler pause // future sampling; this is not time critical, nor dependent on anything else. extern mozilla::Atomic gSkipSampling; void AppendSharedLibraries(mozilla::JSONWriter& aWriter, const SharedLibraryInfo& aInfo); // Convert the array of strings to a bitfield. uint32_t ParseFeaturesFromStringArray(const char** aFeatures, uint32_t aFeatureCount, bool aIsStartup = false); // Add the begin/end 'Awake' markers for the thread. void profiler_mark_thread_awake(); void profiler_mark_thread_asleep(); [[nodiscard]] bool profiler_get_profile_json( SpliceableChunkedJSONWriter& aSpliceableChunkedJSONWriter, double aSinceTime, bool aIsShuttingDown, mozilla::ProgressLogger aProgressLogger); // Flags to conveniently track various JS instrumentations. enum class JSInstrumentationFlags { StackSampling = 0x1, Allocations = 0x2, }; // Write out the information of the active profiling configuration. void profiler_write_active_configuration(mozilla::JSONWriter& aWriter); // Extract all received exit profiles that have not yet expired (i.e., they // still intersect with this process' buffer range). mozilla::Vector profiler_move_exit_profiles(); // If the "MOZ_PROFILER_SYMBOLICATE" env-var is set, we return a new // ProfilerCodeAddressService object to use for local symbolication of profiles. // This is off by default, and mainly intended for local development. mozilla::UniquePtr profiler_code_address_service_for_presymbolication(); extern "C" { // This function is defined in the profiler rust module at // tools/profiler/rust-helper. mozilla::SymbolTable and CompactSymbolTable // have identical memory layout. bool profiler_get_symbol_table(const char* debug_path, const char* breakpad_id, mozilla::SymbolTable* symbol_table); bool profiler_demangle_rust(const char* mangled, char* buffer, size_t len); } // For each running times value, call MACRO(index, name, unit, jsonProperty) #define PROFILER_FOR_EACH_RUNNING_TIME(MACRO) \ MACRO(0, ThreadCPU, Delta, threadCPUDelta) // This class contains all "running times" such as CPU usage measurements. // All measurements are listed in `PROFILER_FOR_EACH_RUNNING_TIME` above. // Each measurement is optional and only takes a value when explicitly set. // Two RunningTimes object may be subtracted, to get the difference between // known values. class RunningTimes { public: constexpr RunningTimes() = default; // Constructor with only a timestamp, useful when no measurements will be // taken. constexpr explicit RunningTimes(const mozilla::TimeStamp& aTimeStamp) : mPostMeasurementTimeStamp(aTimeStamp) {} constexpr void Clear() { *this = RunningTimes{}; } constexpr bool IsEmpty() const { return mKnownBits == 0; } // This should be called right after CPU measurements have been taken. void SetPostMeasurementTimeStamp(const mozilla::TimeStamp& aTimeStamp) { mPostMeasurementTimeStamp = aTimeStamp; } const mozilla::TimeStamp& PostMeasurementTimeStamp() const { return mPostMeasurementTimeStamp; } // Should be filled for any registered thread. #define RUNNING_TIME_MEMBER(index, name, unit, jsonProperty) \ constexpr bool Is##name##unit##Known() const { \ return (mKnownBits & mGot##name##unit) != 0; \ } \ \ constexpr void Clear##name##unit() { \ m##name##unit = 0; \ mKnownBits &= ~mGot##name##unit; \ } \ \ constexpr void Reset##name##unit(uint64_t a##name##unit) { \ m##name##unit = a##name##unit; \ mKnownBits |= mGot##name##unit; \ } \ \ constexpr void Set##name##unit(uint64_t a##name##unit) { \ MOZ_ASSERT(!Is##name##unit##Known(), #name #unit " already set"); \ Reset##name##unit(a##name##unit); \ } \ \ constexpr mozilla::Maybe Get##name##unit() const { \ if (Is##name##unit##Known()) { \ return mozilla::Some(m##name##unit); \ } \ return mozilla::Nothing{}; \ } \ \ constexpr mozilla::Maybe GetJson##name##unit() const { \ if (Is##name##unit##Known()) { \ return mozilla::Some(ConvertRawToJson(m##name##unit)); \ } \ return mozilla::Nothing{}; \ } PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_MEMBER) #undef RUNNING_TIME_MEMBER // Take values from another RunningTimes. RunningTimes& TakeFrom(RunningTimes& aOther) { if (!aOther.IsEmpty()) { #define RUNNING_TIME_TAKE(index, name, unit, jsonProperty) \ if (aOther.Is##name##unit##Known()) { \ Set##name##unit(std::exchange(aOther.m##name##unit, 0)); \ } PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_TAKE) #undef RUNNING_TIME_TAKE aOther.mKnownBits = 0; } return *this; } // Difference from `aBefore` to `this`. Any unknown makes the result unknown. // PostMeasurementTimeStamp set to `this` PostMeasurementTimeStamp, to keep // the most recent timestamp associated with the end of the interval over // which the difference applies. RunningTimes operator-(const RunningTimes& aBefore) const { RunningTimes diff; diff.mPostMeasurementTimeStamp = mPostMeasurementTimeStamp; #define RUNNING_TIME_SUB(index, name, unit, jsonProperty) \ if (Is##name##unit##Known() && aBefore.Is##name##unit##Known()) { \ diff.Set##name##unit(m##name##unit - aBefore.m##name##unit); \ } PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_SUB) #undef RUNNING_TIME_SUB return diff; } private: friend mozilla::ProfileBufferEntryWriter::Serializer; friend mozilla::ProfileBufferEntryReader::Deserializer; // Platform-dependent. static uint64_t ConvertRawToJson(uint64_t aRawValue); mozilla::TimeStamp mPostMeasurementTimeStamp; uint32_t mKnownBits = 0u; #define RUNNING_TIME_MEMBER(index, name, unit, jsonProperty) \ static constexpr uint32_t mGot##name##unit = 1u << index; \ uint64_t m##name##unit = 0; PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_MEMBER) #undef RUNNING_TIME_MEMBER }; template <> struct mozilla::ProfileBufferEntryWriter::Serializer { static Length Bytes(const RunningTimes& aRunningTimes) { Length bytes = 0; #define RUNNING_TIME_SERIALIZATION_BYTES(index, name, unit, jsonProperty) \ if (aRunningTimes.Is##name##unit##Known()) { \ bytes += ULEB128Size(aRunningTimes.m##name##unit); \ } PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_SERIALIZATION_BYTES) #undef RUNNING_TIME_SERIALIZATION_BYTES return ULEB128Size(aRunningTimes.mKnownBits) + bytes; } static void Write(ProfileBufferEntryWriter& aEW, const RunningTimes& aRunningTimes) { aEW.WriteULEB128(aRunningTimes.mKnownBits); #define RUNNING_TIME_SERIALIZE(index, name, unit, jsonProperty) \ if (aRunningTimes.Is##name##unit##Known()) { \ aEW.WriteULEB128(aRunningTimes.m##name##unit); \ } PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_SERIALIZE) #undef RUNNING_TIME_SERIALIZE } }; template <> struct mozilla::ProfileBufferEntryReader::Deserializer { static void ReadInto(ProfileBufferEntryReader& aER, RunningTimes& aRunningTimes) { aRunningTimes = Read(aER); } static RunningTimes Read(ProfileBufferEntryReader& aER) { // Start with empty running times, everything is cleared. RunningTimes times; // This sets all the bits into mKnownBits, we don't need to modify it // further. times.mKnownBits = aER.ReadULEB128(); // For each member that should be known, read its value. #define RUNNING_TIME_DESERIALIZE(index, name, unit, jsonProperty) \ if (times.Is##name##unit##Known()) { \ times.m##name##unit = aER.ReadULEB128(); \ } PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_DESERIALIZE) #undef RUNNING_TIME_DESERIALIZE return times; } }; #endif /* ndef TOOLS_PLATFORM_H_ */