Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cf4bf881 authored by Andy Hung's avatar Andy Hung Committed by Android (Google) Code Review
Browse files

Merge "TimeCheck: Use FixedString"

parents 7a0e8272 35f9615b
Loading
Loading
Loading
Loading
+8 −7
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
#include <android-base/logging.h>
#include <audio_utils/clock.h>
#include <mediautils/EventLog.h>
#include <mediautils/FixedString.h>
#include <mediautils/MethodStatistics.h>
#include <mediautils/TimeCheck.h>
#include <utils/Log.h>
@@ -138,11 +139,11 @@ std::string TimeCheck::toString() {
    return getTimeCheckThread().toString();
}

TimeCheck::TimeCheck(std::string tag, OnTimerFunc&& onTimer, uint32_t timeoutMs,
TimeCheck::TimeCheck(std::string_view tag, OnTimerFunc&& onTimer, uint32_t timeoutMs,
        bool crashOnTimeout)
    : mTimeCheckHandler(new TimeCheckHandler{
            std::move(tag), std::move(onTimer), crashOnTimeout,
            std::chrono::system_clock::now(), gettid()})
    : mTimeCheckHandler{ std::make_shared<TimeCheckHandler>(
            tag, std::move(onTimer), crashOnTimeout,
            std::chrono::system_clock::now(), gettid()) }
    , mTimerHandle(timeoutMs == 0
              ? getTimeCheckThread().trackTask(mTimeCheckHandler->tag)
              : getTimeCheckThread().scheduleTask(
@@ -231,14 +232,14 @@ mediautils::TimeCheck makeTimeCheckStatsForClassMethod(
            mediautils::getStatisticsForClass(className);
    if (!statistics) return {}; // empty TimeCheck.
    return mediautils::TimeCheck(
            std::string(className).append("::").append(methodName),
            [ clazz = std::string(className), method = std::string(methodName),
            FixedString62(className).append("::").append(methodName),
            [ safeMethodName = FixedString30(methodName),
              stats = std::move(statistics) ]
            (bool timeout, float elapsedMs) {
                    if (timeout) {
                        ; // ignored, there is no timeout value.
                    } else {
                        stats->event(method, elapsedMs);
                        stats->event(safeMethodName.asStringView(), elapsedMs);
                    }
            }, 0 /* timeoutMs */);
}
+6 −8
Original line number Diff line number Diff line
@@ -31,17 +31,15 @@ extern std::string formatTime(std::chrono::system_clock::time_point t);
extern std::string_view timeSuffix(std::string_view time1, std::string_view time2);

TimerThread::Handle TimerThread::scheduleTask(
        std::string tag, std::function<void()>&& func, std::chrono::milliseconds timeout) {
        std::string_view tag, std::function<void()>&& func, std::chrono::milliseconds timeout) {
    const auto now = std::chrono::system_clock::now();
    std::shared_ptr<const Request> request{
            new Request{ now, now + timeout, gettid(), std::move(tag) }};
    auto request = std::make_shared<const Request>(now, now + timeout, gettid(), tag);
    return mMonitorThread.add(std::move(request), std::move(func), timeout);
}

TimerThread::Handle TimerThread::trackTask(std::string tag) {
TimerThread::Handle TimerThread::trackTask(std::string_view tag) {
    const auto now = std::chrono::system_clock::now();
    std::shared_ptr<const Request> request{
            new Request{ now, now, gettid(), std::move(tag) }};
    auto request = std::make_shared<const Request>(now, now, gettid(), tag);
    return mNoTimeoutMap.add(std::move(request));
}

@@ -106,10 +104,10 @@ std::string TimerThread::toString(size_t retiredCount) const {
//
/* static */
bool TimerThread::isRequestFromHal(const std::shared_ptr<const Request>& request) {
    const size_t hidlPos = request->tag.find("Hidl");
    const size_t hidlPos = request->tag.asStringView().find("Hidl");
    if (hidlPos == std::string::npos) return false;
    // should be a separator afterwards Hidl which indicates the string was in the class.
    const size_t separatorPos = request->tag.find("::", hidlPos);
    const size_t separatorPos = request->tag.asStringView().find("::", hidlPos);
    return separatorPos != std::string::npos;
}

+282 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.
 */

#pragma once

#include <algorithm>
#include <string>
#include <string_view>

namespace android::mediautils {

/*
 * FixedString is a stack allocatable string buffer that supports
 * simple appending of other strings and string_views.
 *
 * It is designed for no-malloc operation when std::string
 * small buffer optimization is insufficient.
 *
 * To keep code small, use asStringView() for operations on this.
 *
 * Notes:
 * 1) Appending beyond the internal buffer size results in truncation.
 *
 * Alternatives:
 * 1) If you want a sharable copy-on-write string implementation,
 *    consider using the legacy android::String8().
 * 2) Using std::string with a fixed stack allocator may suit your needs,
 *    but exception avoidance is tricky.
 * 3) Using C++20 ranges https://en.cppreference.com/w/cpp/ranges if you don't
 *    need backing store.  Be careful about allocation with ranges.
 *
 * Good small sizes are multiples of 16 minus 2, e.g. 14, 30, 46, 62.
 *
 * Implementation Notes:
 * 1) No iterators or [] for FixedString - please convert to std::string_view.
 * 2) For small N (e.g. less than 62), consider a change to always zero fill and
 *    potentially prevent always zero terminating (if one only does append).
 *
 * Possible arguments to create/append:
 * 1) A FixedString.
 * 2) A single char.
 * 3) A char * pointer.
 * 4) A std::string.
 * 5) A std::string_view (or something convertible to it).
 *
 * Example:
 *
 * FixedString s1(std::string("a"));    // ctor
 * s1 << "bc" << 'd' << '\n';           // streaming append
 * s1 += "hello";                       // += append
 * ASSERT_EQ(s1, "abcd\nhello");
 */
template <uint32_t N>
struct FixedString
{
    // Find the best size type.
    using strsize_t = std::conditional_t<(N > 255), uint32_t, uint8_t>;

    // constructors
    FixedString() { // override default
        buffer_[0] = '\0';
    }

    FixedString(const FixedString& other) { // override default.
        copyFrom<N>(other);
    }

    // The following constructor is not explicit to allow
    // FixedString<8> s = "abcd";
    template <typename ...Types>
    FixedString(Types&&... args) {
        append(std::forward<Types>(args)...);
    }

    // copy assign (copyFrom checks for equality and returns *this).
    FixedString& operator=(const FixedString& other) { // override default.
        return copyFrom<N>(other);
    }

    template <typename ...Types>
    FixedString& operator=(Types&&... args) {
        size_ = 0;
        return append(std::forward<Types>(args)...);
    }

    // operator equals
    bool operator==(const char *s) const {
        return strncmp(c_str(), s, capacity() + 1) == 0;
    }

    bool operator==(std::string_view s) const {
        return size() == s.size() && memcmp(data(), s.data(), size()) == 0;
    }

    // operator not-equals
    template <typename T>
    bool operator!=(const T& other) const {
        return !operator==(other);
    }

    // operator +=
    template <typename ...Types>
    FixedString& operator+=(Types&&... args) {
        return append(std::forward<Types>(args)...);
    }

    // conversion to std::string_view.
    operator std::string_view() const {
        return asStringView();
    }

    // basic observers
    size_t buffer_offset() const { return offsetof(std::decay_t<decltype(*this)>, buffer_); }
    static constexpr uint32_t capacity() { return N; }
    uint32_t size() const { return size_; }
    uint32_t remaining() const { return size_ >= N ? 0 : N - size_; }
    bool empty() const { return size_ == 0; }
    bool full() const { return size_ == N; }  // when full, possible truncation risk.
    char * data() { return buffer_; }
    const char * data() const { return buffer_; }
    const char * c_str() const { return buffer_; }

    inline std::string_view asStringView() const {
        return { buffer_, static_cast<size_t>(size_) };
    }
    inline std::string asString() const {
        return { buffer_, static_cast<size_t>(size_) };
    }

    void clear() { size_ = 0; buffer_[0] = 0; }

    // Implementation of append - using templates
    // to guarantee precedence in the presence of ambiguity.
    //
    // Consider C++20 template overloading through constraints and concepts.
    template <typename T>
    FixedString& append(const T& t) {
        using decayT = std::decay_t<T>;
        if constexpr (is_specialization_v<decayT, FixedString>) {
            // A FixedString<U>
            if (size_ == 0) {
                // optimization to erase everything.
                return copyFrom(t);
            } else {
                return appendStringView({t.data(), t.size()});
            }
        } else if constexpr(std::is_same_v<decayT, char>) {
            if (size_ < N) {
                buffer_[size_++] = t;
                buffer_[size_] = '\0';
            }
            return *this;
        } else if constexpr(std::is_same_v<decayT, char *>) {
            // Some char* ptr.
            return appendString(t);
        } else if constexpr (std::is_convertible_v<decayT, std::string_view>) {
            // std::string_view, std::string, or some other convertible type.
            return appendStringView(t);
        } else /* constexpr */ {
            static_assert(dependent_false_v<T>, "non-matching append type");
        }
    }

    FixedString& appendStringView(std::string_view s) {
        uint32_t total = std::min(static_cast<size_t>(N - size_), s.size());
        memcpy(buffer_ + size_, s.data(), total);
        size_ += total;
        buffer_[size_] = '\0';
        return *this;
    }

    FixedString& appendString(const char *s) {
        // strncpy zero pads to the end,
        // strlcpy returns total expected length,
        // we don't have strncpy_s in Bionic,
        // so we write our own here.
        while (size_ < N && *s != '\0') {
            buffer_[size_++] = *s++;
        }
        buffer_[size_] = '\0';
        return *this;
    }

    // Copy initialize the struct.
    // Note: We are POD but customize the copying for acceleration
    // of moving small strings embedded in a large buffers.
    template <uint32_t U>
    FixedString& copyFrom(const FixedString<U>& other) {
        if ((void*)this != (void*)&other) { // not a self-assignment
            if (other.size() == 0) {
                size_ = 0;
                buffer_[0] = '\0';
                return *this;
            }
            constexpr size_t kSizeToCopyWhole = 64;
            if constexpr (N == U &&
                    sizeof(*this) == sizeof(other) &&
                    sizeof(*this) <= kSizeToCopyWhole) {
                // As we have the same str size type, we can just
                // memcpy with fixed size, which can be easily optimized.
                memcpy(static_cast<void*>(this), static_cast<const void*>(&other), sizeof(*this));
                return *this;
            }
            if constexpr (std::is_same_v<strsize_t, typename FixedString<U>::strsize_t>) {
                constexpr size_t kAlign = 8;  // align to a multiple of 8.
                static_assert((kAlign & (kAlign - 1)) == 0); // power of 2.
                // we check any alignment issues.
                if (buffer_offset() == other.buffer_offset() && other.size() <= capacity()) {
                    // improve on standard POD copying by reducing size.
                    const size_t mincpy = buffer_offset() + other.size() + 1 /* nul */;
                    const size_t maxcpy = std::min(sizeof(*this), sizeof(other));
                    const size_t cpysize = std::min(mincpy + kAlign - 1 & ~(kAlign - 1), maxcpy);
                    memcpy(static_cast<void*>(this), static_cast<const void*>(&other), cpysize);
                    return *this;
                }
            }
            size_ = std::min(other.size(), capacity());
            memcpy(buffer_, other.data(), size_);
            buffer_[size_] = '\0';  // zero terminate.
        }
        return *this;
    }

private:
    //  Template helper methods

    template <typename Test, template <uint32_t> class Ref>
    struct is_specialization : std::false_type {};

    template <template <uint32_t> class Ref, uint32_t UU>
    struct is_specialization<Ref<UU>, Ref>: std::true_type {};

    template <typename Test, template <uint32_t> class Ref>
    static inline constexpr bool is_specialization_v = is_specialization<Test, Ref>::value;

    // For static assert(false) we need a template version to avoid early failure.
    template <typename T>
    static inline constexpr bool dependent_false_v = false;

    // POD variables
    strsize_t size_ = 0;
    char buffer_[N + 1 /* allow zero termination */];
};

// Stream operator syntactic sugar.
// Example:
// s << 'b' << "c" << "d" << '\n';
template <uint32_t N, typename ...Types>
FixedString<N>& operator<<(FixedString<N>& fs, Types&&... args) {
    return fs.append(std::forward<Types>(args)...);
}

// We do not use a default size for fixed string as changing
// the default size would lead to different behavior - we want the
// size to be explicitly known.

// FixedString62 of 62 chars fits in one typical cache line.
using FixedString62 = FixedString<62>;

// Slightly smaller
using FixedString30 = FixedString<30>;

// Since we have added copy and assignment optimizations,
// we are no longer trivially assignable and copyable.
// But we check standard layout here to prevent inclusion of unacceptable members or virtuals.
static_assert(std::is_standard_layout_v<FixedString62>);
static_assert(std::is_standard_layout_v<FixedString30>);

}  // namespace android::mediautils
+12 −2
Original line number Diff line number Diff line
@@ -60,7 +60,7 @@ class TimeCheck {
     *                  the TimeCheck object is destroyed or leaves scope.
     * \param crashOnTimeout true if the object issues an abort on timeout.
     */
    explicit TimeCheck(std::string tag, OnTimerFunc&& onTimer = {},
    explicit TimeCheck(std::string_view tag, OnTimerFunc&& onTimer = {},
            uint32_t timeoutMs = kDefaultTimeOutMs, bool crashOnTimeout = true);

    TimeCheck() = default;
@@ -79,7 +79,17 @@ class TimeCheck {
    // The usage here is const safe.
    class TimeCheckHandler {
    public:
        const std::string tag;
        template <typename S, typename F>
        TimeCheckHandler(S&& _tag, F&& _onTimer, bool _crashOnTimeout,
            const std::chrono::system_clock::time_point& _startTime,
            pid_t _tid)
            : tag(std::forward<S>(_tag))
            , onTimer(std::forward<F>(_onTimer))
            , crashOnTimeout(_crashOnTimeout)
            , startTime(_startTime)
            , tid(_tid)
            {}
        const FixedString62 tag;
        const OnTimerFunc onTimer;
        const bool crashOnTimeout;
        const std::chrono::system_clock::time_point startTime;
+15 −3
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@

#include <android-base/thread_annotations.h>

#include <mediautils/FixedString.h>

namespace android::mediautils {

/**
@@ -54,7 +56,7 @@ class TimerThread {
     * \returns       a handle that can be used for cancellation.
     */
    Handle scheduleTask(
            std::string tag, std::function<void()>&& func, std::chrono::milliseconds timeout);
            std::string_view tag, std::function<void()>&& func, std::chrono::milliseconds timeout);

    /**
     * Tracks a task that shows up on toString() until cancelled.
@@ -62,7 +64,7 @@ class TimerThread {
     * \param tag     string associated with the task.
     * \returns       a handle that can be used for cancellation.
     */
    Handle trackTask(std::string tag);
    Handle trackTask(std::string_view tag);

    /**
     * Cancels a task previously scheduled with scheduleTask()
@@ -129,12 +131,22 @@ class TimerThread {
    // To minimize movement of data, we pass around shared_ptrs to Requests.
    // These are allocated and deallocated outside of the lock.
    struct Request {
        Request(const std::chrono::system_clock::time_point& _scheduled,
                const std::chrono::system_clock::time_point& _deadline,
                pid_t _tid,
                std::string_view _tag)
            : scheduled(_scheduled)
            , deadline(_deadline)
            , tid(_tid)
            , tag(_tag)
            {}

        const std::chrono::system_clock::time_point scheduled;
        const std::chrono::system_clock::time_point deadline; // deadline := scheduled + timeout
                                                              // if deadline == scheduled, no
                                                              // timeout, task not executed.
        const pid_t tid;
        const std::string tag;
        const FixedString62 tag;

        std::string toString() const;
    };
Loading