1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include <thread>
17 
18 #include "rs_profiler.h"
19 #include "rs_profiler_archive.h"
20 #include "rs_profiler_file.h"
21 #include "rs_profiler_network.h"
22 #include "rs_profiler_packet.h"
23 #include "rs_profiler_telemetry.h"
24 #include "rs_profiler_utils.h"
25 
26 namespace OHOS::Rosen {
27 
28 static constexpr uint32_t INACTIVITY_THRESHOLD_SECONDS = 5u;
29 static DeviceInfo g_deviceInfo;
30 static std::mutex g_deviceInfoMutex;
31 static std::mutex g_fileSavingMutex;
32 static bool g_started = false;
33 static double g_inactiveTimestamp = 0.0;
34 static std::vector<std::string> g_records;
35 static double g_recordsTimestamp = 0.0;
36 static double g_currentFrameDirtyRegion = 0.0;
37 
38 // implemented in rs_profiler.cpp
39 void DeviceInfoToCaptureData(double time, const DeviceInfo& in, RSCaptureData& out);
40 
HasInitializationFinished()41 static bool HasInitializationFinished()
42 {
43     constexpr uint32_t maxAttempts = 600u;
44     static uint32_t attempt = 0u;
45     if (attempt < maxAttempts) {
46         attempt++;
47     }
48     return attempt == maxAttempts;
49 }
50 
IsBetaRecordInactive()51 bool RSProfiler::IsBetaRecordInactive()
52 {
53     return (Now() - g_inactiveTimestamp) > INACTIVITY_THRESHOLD_SECONDS;
54 }
55 
RequestVSyncOnBetaRecordInactivity()56 void RSProfiler::RequestVSyncOnBetaRecordInactivity()
57 {
58     if (IsBetaRecordInactive()) {
59         RequestNextVSync();
60     }
61 }
62 
LaunchBetaRecordNotificationThread()63 void RSProfiler::LaunchBetaRecordNotificationThread()
64 {
65     std::thread thread([]() {
66         while (IsBetaRecordStarted()) {
67             RequestVSyncOnBetaRecordInactivity();
68             std::this_thread::sleep_for(std::chrono::seconds(INACTIVITY_THRESHOLD_SECONDS));
69         }
70     });
71     thread.detach();
72 }
73 
LaunchBetaRecordMetricsUpdateThread()74 void RSProfiler::LaunchBetaRecordMetricsUpdateThread()
75 {
76     if (!IsBetaRecordEnabledWithMetrics()) {
77         return;
78     }
79     std::thread thread([]() {
80         while (IsBetaRecordStarted()) {
81             const DeviceInfo deviceInfo = RSTelemetry::GetDeviceInfo();
82 
83             g_deviceInfoMutex.lock();
84             g_deviceInfo = deviceInfo;
85             g_deviceInfoMutex.unlock();
86 
87             constexpr int32_t sendInterval = 8;
88             std::this_thread::sleep_for(std::chrono::milliseconds(sendInterval));
89         }
90     });
91     thread.detach();
92 }
93 
WriteBetaRecordFileThread(RSFile & file,const std::string path)94 void RSProfiler::WriteBetaRecordFileThread(RSFile& file, const std::string path)
95 {
96     std::vector<uint8_t> fileData;
97     if (!file.GetDataCopy(fileData)) {
98         return;
99     }
100 
101     std::thread thread([fileDataCopy{std::move(fileData)}, path]() {
102         const std::lock_guard<std::mutex> fileSavingMutex(g_fileSavingMutex);
103 
104         FILE* fileCopy = Utils::FileOpen(path, "wbe");
105         if (!Utils::IsFileValid(fileCopy)) {
106             return;
107         }
108         Utils::FileWrite(fileCopy, fileDataCopy.data(), fileDataCopy.size());
109         Utils::FileClose(fileCopy);
110     });
111     thread.detach();
112 }
113 
RenameAndSendFilenameThread()114 void RSProfiler::RenameAndSendFilenameThread()
115 {
116     std::thread thread([]() {
117         const std::lock_guard<std::mutex> fileSavingMutex(g_fileSavingMutex);
118         SendBetaRecordPath();
119     });
120     thread.detach();
121 }
122 
StartBetaRecord()123 void RSProfiler::StartBetaRecord()
124 {
125     if (HasInitializationFinished() && !IsBetaRecordStarted() && IsBetaRecordEnabled()) {
126         g_started = true;
127         g_inactiveTimestamp = Now();
128 
129         LaunchBetaRecordNotificationThread();
130         LaunchBetaRecordMetricsUpdateThread();
131 
132         // Start recording for the first file
133         RecordStart(ArgList());
134     }
135 }
136 
IsBetaRecordStarted()137 bool RSProfiler::IsBetaRecordStarted()
138 {
139     return g_started;
140 }
141 
SendBetaRecordPath()142 void RSProfiler::SendBetaRecordPath()
143 {
144     if (g_records.empty()) {
145         return;
146     }
147 
148     const std::string directory = Utils::GetDirectory(g_records[0]);
149 
150     std::string path;
151     for (size_t i = 0; i < g_records.size(); i++) {
152         const auto& original = g_records[i];
153         const auto renamed = Utils::MakePath(directory, "rec_" + std::to_string(g_records.size() - i - 1) + ".ohr");
154         const auto message = "BetaRecord: Rename: " + original + " -> " + renamed;
155         if (std::rename(original.data(), renamed.data())) {
156             Network::SendMessage(message + "failed");
157             path.clear();
158             break;
159         }
160 
161         Network::SendMessage(message);
162         path += renamed + ";";
163     }
164     g_records.clear();
165 
166     Network::SendBetaRecordPath(path);
167 }
168 
SaveBetaRecord()169 bool RSProfiler::SaveBetaRecord()
170 {
171     if (!IsBetaRecordSavingTriggered()) {
172         return false;
173     }
174 
175     RecordStop(ArgList());
176     RenameAndSendFilenameThread();
177     EnableBetaRecord();
178     RecordStart(ArgList());
179     return true;
180 }
181 
UpdateBetaRecord()182 void RSProfiler::UpdateBetaRecord()
183 {
184     if (!IsBetaRecordStarted()) {
185         return;
186     }
187 
188     if (!IsBetaRecordEnabled()) {
189         RecordStop(ArgList());
190         g_started = false;
191     }
192 
193     if (!IsRecording()) {
194         return;
195     }
196 
197     if (!SaveBetaRecord()) {
198         constexpr uint32_t recordMaxLengthSeconds = 30u; // 30sec length of each recording
199         const double recordLength = Now() - g_recordsTimestamp;
200         if (recordLength > recordMaxLengthSeconds) {
201             RecordStop(ArgList());
202             RecordStart(ArgList());
203         }
204     }
205 
206     // the last time any rendering is done
207     if (g_currentFrameDirtyRegion > 0) {
208         g_inactiveTimestamp = Now();
209     }
210 }
211 
OpenBetaRecordFile(RSFile & file)212 bool RSProfiler::OpenBetaRecordFile(RSFile& file)
213 {
214     if (!IsBetaRecordStarted()) {
215         return false;
216     }
217 
218     const auto path = "RECORD_IN_MEMORY";
219     file.Create(path);
220 
221     g_recordsTimestamp = Now();
222     return true;
223 }
224 
SaveBetaRecordFile(RSFile & file)225 bool RSProfiler::SaveBetaRecordFile(RSFile& file)
226 {
227     if (!IsBetaRecordStarted()) {
228         return false;
229     }
230 
231     const std::string cacheFile("data/service/el0/render_service/file0");
232     constexpr uint32_t maxCacheFiles = 5u; // 5 recordings in a "ring buffer" way
233 
234     static uint32_t index = 0u;
235     const auto path = cacheFile + std::to_string(index++) + ".ohr";
236     if (index >= maxCacheFiles) {
237         index = 0u;
238     }
239 
240     WriteBetaRecordFileThread(file, path);
241 
242     constexpr uint32_t maxRecords = 4u;
243     if (g_records.size() < maxRecords) {
244         g_records.push_back(path);
245     } else {
246         std::rotate(g_records.begin(), g_records.begin() + 1, g_records.end());
247         g_records[g_records.size() - 1] = path;
248     }
249 
250     return true;
251 }
252 
WriteBetaRecordMetrics(RSFile & file,double time)253 void RSProfiler::WriteBetaRecordMetrics(RSFile& file, double time)
254 {
255     if (!IsBetaRecordStarted() || (time < 0.0) || !IsBetaRecordEnabledWithMetrics()) {
256         return;
257     }
258 
259     g_deviceInfoMutex.lock();
260     const DeviceInfo deviceInfo = g_deviceInfo;
261     g_deviceInfoMutex.unlock();
262 
263     RSCaptureData captureData;
264     DeviceInfoToCaptureData(time, deviceInfo, captureData);
265 
266     std::vector<char> out;
267     DataWriter archive(out);
268     char headerType = static_cast<char>(PackageID::RS_PROFILER_GFX_METRICS);
269     archive.Serialize(headerType);
270     captureData.Serialize(archive);
271 
272     file.WriteGFXMetrics(0, time, 0, out.data(), out.size());
273 }
274 
UpdateDirtyRegionBetaRecord(double currentFrameDirtyRegion)275 void RSProfiler::UpdateDirtyRegionBetaRecord(double currentFrameDirtyRegion)
276 {
277     g_currentFrameDirtyRegion = currentFrameDirtyRegion;
278 }
279 
280 } // namespace OHOS::Rosen