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 "timers.h"
17#include "os/file.h"
18
19#include <algorithm>
20#include <cerrno>
21#include <cstring>
22#include <fstream>
23#include <iomanip>
24#include <iostream>
25#include <sstream>
26
27namespace panda {
28TimeStartFunc Timer::timerStart = Timer::TimerStartDoNothing;
29TimeEndFunc Timer::timerEnd = Timer::TimerEndDoNothing;
30std::unordered_map<std::string_view, TimeRecord> Timer::timers_;
31std::vector<std::string_view> Timer::events_;
32std::mutex Timer::mutex_;
33std::string Timer::perfFile_;
34
35void Timer::InitializeTimer(std::string &perfFile)
36{
37    if (!perfFile.empty()) {
38        Timer::timerStart = Timer::TimerStartImpl;
39        Timer::timerEnd = Timer::TimerEndImpl;
40        perfFile_ = perfFile;
41    }
42}
43
44void WriteFile(std::stringstream &ss, std::string &perfFile)
45{
46    std::ofstream fs;
47    fs.open(panda::os::file::File::GetExtendedFilePath(perfFile));
48    if (!fs.is_open()) {
49        std::cerr << "Failed to open perf file: " << perfFile << ". Errro: " << std::strerror(errno) << std::endl;
50        return;
51    }
52    fs << ss.str();
53    fs.close();
54}
55
56bool DescentComparator(const std::pair<std::string, double> p1, const std::pair<std::string, double> p2)
57{
58    return p1.second > p2.second;
59}
60
61void ProcessTimePointRecord(
62    const std::string& file,
63    const TimePointRecord& timePointRecord,
64    double& eventTime,
65    std::vector<std::pair<std::string, double>>& eachFileTime,
66    std::vector<std::string>& incompleteRecords)
67{
68    if (timePointRecord.startTime != std::chrono::steady_clock::time_point() &&
69        timePointRecord.endTime != std::chrono::steady_clock::time_point()) {
70        auto duration = timePointRecord.endTime - timePointRecord.startTime;
71        if (duration.count() >= 0) {
72            auto t = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
73            eventTime += t;
74            if (!file.empty()) {
75                eachFileTime.emplace_back(file, t);
76            }
77        } else {
78            incompleteRecords.push_back(file + ": The end time is earlier than the start time");
79        }
80    } else {
81        incompleteRecords.push_back(file + ": " +
82            (timePointRecord.startTime == std::chrono::steady_clock::time_point() ?
83            "Lack of start time" : "Lack of end time"));
84    }
85}
86
87void Timer::PrintTimers()
88{
89    std::stringstream ss;
90    ss << "------------- Compilation time consumption in milliseconds: -------------" << std::endl;
91    ss << "Note: When compiling multiple files in parallel, " <<
92          "we will track the time consumption of each file individually. The output will aggregate these times, " <<
93          "potentially resulting in a total time consumption less than the sum of individual file times." <<
94          std::endl << std::endl;
95
96    std::vector<std::string> summedUpTimeString;
97    ss << "------------- Compilation time consumption of each file: ----------------" << std::endl;
98
99    for (auto &event: events_) {
100        auto &timeRecord = timers_.at(event);
101        auto formattedEvent =
102            std::string(timeRecord.level, ' ') + std::string(timeRecord.level, '#') + std::string(event);
103
104        double eventTime = 0.0;
105        std::vector<std::pair<std::string, double>> eachFileTime;
106        std::vector<std::string> incompleteRecords;
107
108        for (const auto &[file, timePointRecord] : timeRecord.timePoints) {
109            ProcessTimePointRecord(file, timePointRecord, eventTime, eachFileTime, incompleteRecords);
110        }
111
112        // print each file time consumption in descending order
113        if (!eachFileTime.empty()) {
114            std::sort(eachFileTime.begin(), eachFileTime.end(), DescentComparator);
115            ss << formattedEvent << ", time consumption of each file:" << std::endl;
116        }
117        for (auto &pair : eachFileTime) {
118            if (!pair.first.empty()) {
119                ss << pair.first << ": " << pair.second << " ms" <<std::endl;
120            }
121        }
122
123        // Print incomplete records
124        if (!incompleteRecords.empty()) {
125            ss << formattedEvent << ", Incomplete records:" << std::endl;
126            for (const auto& record : incompleteRecords) {
127                ss << "  " << record << std::endl;
128            }
129        }
130
131        // collect the sum of each file's time consumption
132        std::stringstream eventSummedUpTimeStream;
133        eventSummedUpTimeStream << formattedEvent << ": " << eventTime << " ms";
134        summedUpTimeString.push_back(eventSummedUpTimeStream.str());
135    }
136    ss << "------------- Compilation time consumption summed up: -------------------" << std::endl;
137    for (auto &str: summedUpTimeString) {
138        ss << str << std::endl;
139    }
140    ss << "-------------------------------------------------------------------------" << std::endl;
141    WriteFile(ss, perfFile_);
142}
143
144void Timer::TimerStartImpl(const std::string_view event, std::string fileName)
145{
146    TimePointRecord tpr;
147    tpr.startTime = std::chrono::steady_clock::now();
148    int level = 0;
149    auto eventIter = eventMap.find(event);
150    if (eventIter != eventMap.end()) {
151        level = eventIter->second;
152    } else {
153        std::cerr << "Undefined event: " << event << ". Please check!" << std::endl;
154    }
155
156    std::unique_lock<std::mutex> lock(mutex_);
157    auto iter = timers_.find(event);
158    if (iter != timers_.end()) {
159        iter->second.timePoints.emplace(fileName, tpr);
160    } else {
161        TimeRecord tr;
162        tr.timePoints.emplace(fileName, tpr);
163        tr.event = event;
164        tr.level = level;
165
166        timers_.emplace(event, tr);
167        events_.push_back(event);
168    }
169}
170
171void Timer::TimerEndImpl(const std::string_view event, std::string fileName)
172{
173    auto endTime = std::chrono::steady_clock::now();
174    std::unique_lock<std::mutex> lock(mutex_);
175    auto time_record_iter = timers_.find(event);
176    if (time_record_iter == timers_.end()) {
177        std::cerr << "Event " << event << " not found in records, skip record end time!" << std::endl;
178        return;
179    }
180    auto& timePoints = time_record_iter->second.timePoints;
181    auto time_point_iter = timePoints.find(fileName);
182    if (time_point_iter == timePoints.end()) {
183        std::cerr << "Event " << event << " and file " << fileName <<
184            " start timer not found in records, skip record end time!" << std::endl;
185        return;
186    }
187    time_point_iter->second.endTime = endTime;
188}
189
190}  // namespace panda
191