Revision control

Copy as Markdown

// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <private/qhitrace_p.h>
#include <private/qtools_p.h>
#include <QtCore/qlogging.h>
#include <hitrace/trace.h>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <type_traits>
QT_BEGIN_NAMESPACE
namespace {
constexpr char elidedMark[] = "...";
constexpr char16_t firstPrintable = 0x20;
constexpr char16_t lastPrintable = 0x7e;
constexpr char escapeMark = '\\';
constexpr char unicodeEscape = 'u';
constexpr char separatorReplacement = ' ';
constexpr int escapeDigits = 2 * sizeof(char16_t);
constexpr int bitsPerHexDigit = 4;
constexpr qsizetype shortEscapeSize = 2;
constexpr qsizetype unicodeEscapeSize = 2 + escapeDigits;
constexpr int32_t unregisteredListener = -1;
Q_CONSTINIT int32_t qtHiTraceListener = unregisteredListener;
bool isSeparator(QChar unit)
{
return unit == u',' || unit == u'=' || unit == u'|';
}
bool isWrittenVerbatim(QChar unit)
{
return unit.unicode() >= firstPrintable && unit.unicode() <= lastPrintable
&& unit != QLatin1Char(escapeMark);
}
char shortEscape(QChar unit)
{
switch (unit.unicode()) {
case '\\':
return '\\';
case '\b':
return 'b';
case '\f':
return 'f';
case '\n':
return 'n';
case '\r':
return 'r';
case '\t':
return 't';
default:
return 0;
}
}
qsizetype escapedSize(QChar unit)
{
if (isWrittenVerbatim(unit))
return 1;
return shortEscape(unit) ? shortEscapeSize : unicodeEscapeSize;
}
QByteArray escaped(QStringView text)
{
QByteArray result;
result.reserve(text.size());
for (const QChar unit : text) {
if (isSeparator(unit)) {
result += separatorReplacement;
continue;
}
if (isWrittenVerbatim(unit)) {
result += char(unit.unicode());
continue;
}
result += escapeMark;
if (const char shortForm = shortEscape(unit)) {
result += shortForm;
continue;
}
result += unicodeEscape;
for (int digit = escapeDigits - 1; digit >= 0; --digit)
result += QtMiscUtils::toHexLower(unit.unicode() >> (digit * bitsPerHexDigit));
}
return result;
}
struct FittingUnits
{
qsizetype count = 0;
qsizetype escapedSize = 0;
};
template <typename Iterator>
bool startsSurrogatePair(Iterator unit, Iterator end)
{
constexpr bool isReversed = std::is_same_v<Iterator, QStringView::const_reverse_iterator>;
if (unit + 1 == end)
return false;
const QChar high = isReversed ? unit[1] : unit[0];
const QChar low = isReversed ? unit[0] : unit[1];
return high.isHighSurrogate() && low.isLowSurrogate();
}
template <typename Iterator>
FittingUnits fittingUnits(Iterator unit, Iterator end, qsizetype maximumSize)
{
FittingUnits fitting;
while (unit != end) {
const qsizetype step = startsSurrogatePair(unit, end) ? 2 : 1;
qsizetype stepSize = escapedSize(unit[0]);
if (step == 2)
stepSize += escapedSize(unit[1]);
if (fitting.escapedSize + stepSize > maximumSize)
break;
fitting.escapedSize += stepSize;
fitting.count += step;
unit += step;
}
return fitting;
}
QByteArray elided(QStringView value, qsizetype maximumSize)
{
Q_ASSERT(maximumSize >= qtHiTraceSmallestValueBudget);
if (fittingUnits(value.cbegin(), value.cend(), maximumSize).count == value.size())
return escaped(value);
const qsizetype kept = maximumSize - (qsizetype(sizeof(elidedMark)) - 1);
const FittingUnits head = fittingUnits(value.cbegin(), value.cend(), kept / 2);
const FittingUnits tail =
fittingUnits(value.crbegin(), value.crend(), kept - head.escapedSize);
return escaped(value.first(head.count)) + elidedMark + escaped(value.last(tail.count));
}
void qtHiTraceStatusChanged(bool enabled)
{
qtHiTraceEnabled.storeRelaxed(enabled);
}
void qtHiTraceWatchStatus()
{
const int32_t listener = OH_HiTrace_RegisterTraceListener(qtHiTraceStatusChanged);
if (listener < 0) {
qtHiTraceEnabled.storeRelaxed(true);
qWarning("Qt tracing: OH_HiTrace_RegisterTraceListener() failed with %d, so tracepoint "
"arguments are rendered whether or not a capture is running",
listener);
return;
}
qtHiTraceListener = listener;
if (OH_HiTrace_IsTraceEnabled())
qtHiTraceEnabled.storeRelaxed(true);
}
void qtHiTraceUnwatchStatus()
{
if (qtHiTraceListener != unregisteredListener)
OH_HiTrace_UnregisterTraceListener(qtHiTraceListener);
}
} // namespace
QByteArray qtHiTraceArgument(const char *value, qsizetype maximumSize)
{
return elided(QString::fromUtf8(value), maximumSize);
}
QByteArray qtHiTraceArgument(const QString &value, qsizetype maximumSize)
{
return elided(value, maximumSize);
}
const char *qtHiTraceFormatArguments(int sliceNameSize, const char *format, ...)
{
Q_CONSTINIT static thread_local char renderedArguments[qtHiTraceRecordBudget] = {};
const int budget = qtHiTraceRecordBudget - sliceNameSize;
if (budget <= int(sizeof(elidedMark)))
return "";
va_list values;
va_start(values, format);
const int renderedSize = std::vsnprintf(renderedArguments, budget, format, values);
va_end(values);
if (renderedSize < 0)
return "";
if (renderedSize >= budget) {
std::memcpy(renderedArguments + budget - sizeof(elidedMark), elidedMark,
sizeof(elidedMark));
}
return renderedArguments;
}
Q_CONSTINIT QBasicAtomicInteger<bool> qtHiTraceEnabled = Q_BASIC_ATOMIC_INITIALIZER(false);
Q_CONSTRUCTOR_FUNCTION(qtHiTraceWatchStatus)
Q_DESTRUCTOR_FUNCTION(qtHiTraceUnwatchStatus)
QT_END_NAMESPACE