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 "SceneTimerOhImpl.h"
17#include <sys/prctl.h>
18#include <chrono>
19#include <thread>
20#include "hiview_logger.h"
21
22namespace OHOS {
23namespace HiviewDFX {
24DEFINE_LOG_LABEL(0xD002D66, "Hiview-XPerformance");
25
26using namespace std::chrono;
27
28const static std::string TIMER_THREAD_NAME = "XperfTimerThr";
29
30SceneTimerOhImpl::SceneTimerOhImpl()
31{
32    std::thread timerThread(&SceneTimerOhImpl::Loop, this);
33    timerThread.detach();
34}
35
36void SceneTimerOhImpl::RegUser(int userId, ICb* cb)
37{
38    callbacks[userId] = cb;
39}
40
41void SceneTimerOhImpl::UnRegUser(int userId)
42{
43    callbacks.erase(userId);
44}
45
46void SceneTimerOhImpl::Start(int user, int id, long interval)
47{
48    int key = BuildRecordKey(user, id);
49    ValidateDuplication(key);
50    long long expireTs = CalcExpireTimeStamp(interval);
51    FillRecordAndNotify(key, expireTs);
52}
53
54void SceneTimerOhImpl::Stop(int user, int id)
55{
56    int key = BuildRecordKey(user, id);
57    ValidateExistence(key);
58    RemoveRecordAndNotify(key);
59}
60
61void SceneTimerOhImpl::Loop()
62{
63    std::unique_lock<std::mutex> uniqueLock(mut);
64    prctl(PR_SET_NAME, TIMER_THREAD_NAME.c_str(), nullptr, nullptr, nullptr);
65    milliseconds interval(checkInterval);
66    while (true) {
67        if (!records.empty()) {
68            cv.wait_for(uniqueLock, interval);
69            /* note the lock is held now */
70            CheckRecordsAndTrigger();
71            continue;
72        }
73        cv.wait(uniqueLock);
74    }
75}
76
77void SceneTimerOhImpl::FillRecordAndNotify(int key, long long expireTs)
78{
79    std::unique_lock<std::mutex> uniqueLock(mut);
80    // I believe this double-check is needed
81    if (records.find(key) != records.end()) {
82        throw std::invalid_argument("duplicated id");
83    }
84    records.emplace(key, expireTs);
85    cv.notify_all(); // remember this
86}
87
88void SceneTimerOhImpl::ValidateDuplication(int key)
89{
90    std::unique_lock<std::mutex> uniqueLock(mut);
91    if (records.find(key) != records.end()) {
92        throw std::invalid_argument("duplicated id");
93    }
94}
95
96void SceneTimerOhImpl::ValidateExistence(int key)
97{
98    std::unique_lock<std::mutex> uniqueLock(mut);
99    if (records.find(key) == records.end()) {
100        throw std::invalid_argument("non-existing id");
101    }
102}
103
104long long SceneTimerOhImpl::GetCurTimeStamp()
105{
106    milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
107    return ms.count();
108}
109
110/* already have lock */
111void SceneTimerOhImpl::CheckRecordsAndTrigger()
112{
113    long long nowMs = GetCurTimeStamp();
114    for (std::map<int, long long>::iterator it = records.begin(); it != records.end();) {
115        long long expire = it->second;
116        int key = it->first;
117        if (nowMs >= expire) {
118            it = records.erase(it);
119            TriggerCallbak(key);
120        } else {
121            it++;
122        }
123    }
124}
125
126int SceneTimerOhImpl::BuildRecordKey(int user, int id)
127{
128    if (user >= maxUserId) {
129        throw std::invalid_argument("exceeds max user id");
130    }
131    if (id >= maxRecordPerUser) {
132        throw std::invalid_argument("exceeds max id per user");
133    }
134    int ret = user * maxRecordPerUser + id;
135    return ret;
136}
137
138long long SceneTimerOhImpl::CalcExpireTimeStamp(long delay)
139{
140    long long nowMs = GetCurTimeStamp();
141    long long expire = nowMs + delay;
142    return expire;
143}
144
145void SceneTimerOhImpl::TriggerCallbak(int recordKey)
146{
147    int user = ExtractUserFromRecordKey(recordKey);
148    int id = ExtractIdFromRecordKey(recordKey);
149    try {
150        ICb* cb = callbacks.at(user);
151        if (cb == nullptr) {
152            HIVIEW_LOGE("SceneTimerImpl::TriggerCallbak cb is null");
153            return;
154        }
155        cb->Expired(id);
156    }
157    catch (const std::out_of_range& outex) {
158        HIVIEW_LOGE("SceneTimerImpl::back id=%{public}d;user=%{public}d;key=%{public}d;", id, user, recordKey);
159    }
160}
161
162int SceneTimerOhImpl::ExtractUserFromRecordKey(int key)
163{
164    return key / maxRecordPerUser;
165}
166
167int SceneTimerOhImpl::ExtractIdFromRecordKey(int key)
168{
169    return key % maxRecordPerUser;
170}
171
172void SceneTimerOhImpl::RemoveRecordAndNotify(int key)
173{
174    std::unique_lock<std::mutex> uniqueLock(mut);
175    records.erase(records.find(key));
176    cv.notify_all();
177}
178} // HiviewDFX
179} // OHOS