1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qqmljscompilerstats_p.h"
5
6#include <QFile>
7#include <QJsonArray>
8#include <QJsonDocument>
9#include <QJsonObject>
10#include <QTextStream>
11
12QT_BEGIN_NAMESPACE
13
14namespace QQmlJS {
15
16using namespace Qt::StringLiterals;
17
18std::unique_ptr<AotStats> QQmlJSAotCompilerStats::s_instance = std::make_unique<AotStats>();
19QString QQmlJSAotCompilerStats::s_moduleId;
20bool QQmlJSAotCompilerStats::s_recordAotStats = false;
21
22bool QQmlJS::AotStatsEntry::operator<(const AotStatsEntry &other) const
23{
24 if (line == other.line)
25 return column < other.column;
26 return line < other.line;
27}
28
29void AotStats::insert(const AotStats &other)
30{
31 for (const auto &[moduleUri, moduleStats] : other.m_entries.asKeyValueRange()) {
32 m_entries[moduleUri].insert(hash: moduleStats);
33 }
34}
35
36std::optional<QList<QString>> AotStats::readAllLines(const QString &path)
37{
38 QFile aotstatsListFile(path);
39 if (!aotstatsListFile.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
40 qDebug().noquote() << u"Could not open \"%1\" for reading"_s.arg(a: aotstatsListFile.fileName());
41 return std::nullopt;
42 }
43
44 QStringList aotstatsFiles;
45 QTextStream stream(&aotstatsListFile);
46 while (!stream.atEnd())
47 aotstatsFiles.append(t: stream.readLine());
48
49 return aotstatsFiles;
50}
51
52std::optional<AotStats> AotStats::parseAotstatsFile(const QString &aotstatsPath)
53{
54 QFile file(aotstatsPath);
55 if (!file.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
56 qDebug().noquote() << u"Could not open \"%1\""_s.arg(a: aotstatsPath);
57 return std::nullopt;
58 }
59
60 return AotStats::fromJsonDocument(QJsonDocument::fromJson(json: file.readAll()));
61}
62
63std::optional<AotStats> AotStats::aggregateAotstatsList(const QString &aotstatsListPath)
64{
65 const auto aotstatsFiles = readAllLines(path: aotstatsListPath);
66 if (!aotstatsFiles.has_value())
67 return std::nullopt;
68
69 AotStats aggregated;
70 if (aotstatsFiles->empty())
71 return aggregated;
72
73 for (const auto &aotstatsFile : aotstatsFiles.value()) {
74 auto parsed = parseAotstatsFile(aotstatsPath: aotstatsFile);
75 if (!parsed.has_value())
76 return std::nullopt;
77 aggregated.insert(other: parsed.value());
78 }
79
80 return aggregated;
81}
82
83static constexpr int S_AOTSTATS_FORMAT_REVISION = 1; // Added support for skipping
84
85static constexpr QLatin1StringView S_CODEGEN_RESULT{ "codegenResult" };
86static constexpr QLatin1StringView S_COLUMN{ "column" };
87static constexpr QLatin1StringView S_DURATION_MICROSECONDS{ "durationMicroseconds" };
88static constexpr QLatin1StringView S_ENTRIES{ "entries" };
89static constexpr QLatin1StringView S_FILE_PATH{ "filePath" };
90static constexpr QLatin1StringView S_FORMAT_REVISION{ "formatRevision" };
91static constexpr QLatin1StringView S_FUNCTION_NAME{ "functionName" };
92static constexpr QLatin1StringView S_LINE{ "line" };
93static constexpr QLatin1StringView S_MESSAGE{ "message" };
94static constexpr QLatin1StringView S_MODULES{ "modules" };
95static constexpr QLatin1StringView S_MODULE_FILES{ "moduleFiles" };
96static constexpr QLatin1StringView S_MODULE_ID{ "moduleId" };
97
98std::optional<AotStats> AotStats::fromJsonDocument(const QJsonDocument &document)
99{
100 QJsonObject root = document.object();
101 const QJsonValue revision = root[S_FORMAT_REVISION];
102 if (revision.isUndefined() || revision.toInt() != S_AOTSTATS_FORMAT_REVISION) {
103 qDebug() << "AotStats format revision missmatch. Please try again with a clean build.";
104 return std::nullopt;
105 }
106
107 const QJsonArray modulesArray = root[S_MODULES].toArray();
108 QQmlJS::AotStats result;
109 for (const auto &modulesArrayEntry : std::as_const(t: modulesArray)) {
110 const auto &moduleObject = modulesArrayEntry.toObject();
111 QString moduleId = moduleObject[S_MODULE_ID].toString();
112 const QJsonArray &filesArray = moduleObject[S_MODULE_FILES].toArray();
113
114 QHash<QString, QList<AotStatsEntry>> files;
115 for (const auto &filesArrayEntry : filesArray) {
116 const QJsonObject &fileObject = filesArrayEntry.toObject();
117 QString filepath = fileObject[S_FILE_PATH].toString();
118 const QJsonArray &statsArray = fileObject[S_ENTRIES].toArray();
119
120 QList<AotStatsEntry> stats;
121 for (const auto &statsArrayEntry : statsArray) {
122 const auto &statsObject = statsArrayEntry.toObject();
123 QQmlJS::AotStatsEntry stat;
124 auto micros = statsObject[S_DURATION_MICROSECONDS].toInteger();
125 stat.codegenDuration = std::chrono::microseconds(micros);
126 stat.functionName = statsObject[S_FUNCTION_NAME].toString();
127 stat.message = statsObject[S_MESSAGE].toString();
128 stat.line = statsObject[S_LINE].toInt();
129 stat.column = statsObject[S_COLUMN].toInt();
130 stat.codegenResult = QQmlJS::CodegenResult(statsObject[S_CODEGEN_RESULT].toInt());
131 stats.append(t: std::move(stat));
132 }
133
134 std::sort(first: stats.begin(), last: stats.end());
135 files[filepath] = std::move(stats);
136 }
137
138 result.m_entries[moduleId] = std::move(files);
139 }
140
141 return result;
142}
143
144QJsonDocument AotStats::toJsonDocument() const
145{
146 QJsonArray modulesArray;
147 for (auto it1 = m_entries.begin(); it1 != m_entries.end(); ++it1) {
148 const QString moduleId = it1.key();
149 const QHash<QString, QList<AotStatsEntry>> &files = it1.value();
150
151 QJsonArray filesArray;
152 for (auto it2 = files.begin(); it2 != files.end(); ++it2) {
153 const QString &filename = it2.key();
154 const QList<AotStatsEntry> &stats = it2.value();
155
156 QJsonArray statsArray;
157 for (const auto &stat : stats) {
158 QJsonObject statObject;
159 auto micros = static_cast<qint64>(stat.codegenDuration.count());
160 statObject.insert(key: S_DURATION_MICROSECONDS, value: micros);
161 statObject.insert(key: S_FUNCTION_NAME, value: stat.functionName);
162 statObject.insert(key: S_MESSAGE, value: stat.message);
163 statObject.insert(key: S_LINE, value: stat.line);
164 statObject.insert(key: S_COLUMN, value: stat.column);
165 using CodegenResType = std::underlying_type_t<QQmlJS::CodegenResult>;
166 statObject.insert(key: S_CODEGEN_RESULT,
167 value: static_cast<CodegenResType>(stat.codegenResult));
168 statsArray.append(value: statObject);
169 }
170
171 QJsonObject o;
172 o.insert(key: S_FILE_PATH, value: filename);
173 o.insert(key: S_ENTRIES, value: statsArray);
174 filesArray.append(value: o);
175 }
176
177 QJsonObject o;
178 o.insert(key: S_MODULE_ID, value: moduleId);
179 o.insert(key: S_MODULE_FILES, value: filesArray);
180 modulesArray.append(value: o);
181 }
182
183 QJsonObject root;
184 root.insert(key: S_FORMAT_REVISION, value: S_AOTSTATS_FORMAT_REVISION);
185 root.insert(key: S_MODULES, value: modulesArray);
186 return QJsonDocument(root);
187}
188
189void AotStats::registerFile(const QString &moduleId, const QString &filepath)
190{
191 m_entries[moduleId][filepath] = {};
192}
193
194void AotStats::addEntry(
195 const QString &moduleId, const QString &filepath, const AotStatsEntry &entry)
196{
197 m_entries[moduleId][filepath].append(t: entry);
198}
199
200bool AotStats::saveToDisk(const QString &filepath) const
201{
202 QFile file(filepath);
203 if (!file.open(flags: QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
204 qDebug().noquote() << u"Could not open \"%1\""_s.arg(a: filepath);
205 return false;
206 }
207
208 file.write(data: this->toJsonDocument().toJson(format: QJsonDocument::Indented));
209 return true;
210}
211
212void QQmlJSAotCompilerStats::registerFile(const QString &filepath)
213{
214 QQmlJSAotCompilerStats::instance()->registerFile(moduleId: s_moduleId, filepath);
215}
216
217void QQmlJSAotCompilerStats::addEntry(const QString &filepath, const QQmlJS::AotStatsEntry &entry)
218{
219 QQmlJSAotCompilerStats::instance()->addEntry(moduleId: s_moduleId, filepath, entry);
220}
221
222} // namespace QQmlJS
223
224QT_END_NAMESPACE
225

source code of qtdeclarative/src/qmlcompiler/qqmljscompilerstats.cpp