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#ifndef ECMASCRIPT_PGO_PROFILER_PGO_TRACE_H
17#define ECMASCRIPT_PGO_PROFILER_PGO_TRACE_H
18
19#include <iomanip>
20#include <memory>
21#include <numeric>
22#include <unordered_map>
23
24#include "ecmascript/method.h"
25
26namespace panda::ecmascript::pgo {
27class PGOTrace {
28public:
29    static constexpr int NAME_WIDTH = 40;
30    static constexpr int WIDTH = 20;
31    static constexpr int COLUMN_WIDTH = NAME_WIDTH + WIDTH * 6;
32    static constexpr const char* TITLE_TEXT = " PGO Trace ";
33    static constexpr const float MIN_PRINT_TIME = 10.0;
34    static constexpr const int MIN_PRINT_COUNT = 10;
35    static constexpr const int PRECISION = 3;
36    static constexpr const int TOTAL_COLUMN = 6;
37    static constexpr const int PROFILE_BYTECODE_TIME_COLUMN = 2;
38
39    static std::string Title()
40    {
41        int halfWidth = (COLUMN_WIDTH - std::strlen(TITLE_TEXT)) / 2 + 1;
42        return std::string(halfWidth, '=') + TITLE_TEXT + std::string(halfWidth, '=');
43    }
44
45    static std::string Separator()
46    {
47        return std::string(Title().length(), '=');
48    }
49
50    static std::shared_ptr<PGOTrace> GetInstance()
51    {
52        static auto trace = std::make_shared<PGOTrace>();
53        return trace;
54    }
55
56    void SetEnable(bool enable)
57    {
58        LOG_ECMA(DEBUG) << "PGO Trace enable: " << enable;
59        enable_ = enable;
60    }
61
62    bool IsEnable() const
63    {
64        return enable_;
65    }
66
67    class MethodData {
68    public:
69        MethodData(JSTaggedValue value, bool hotness)
70        {
71            Method* method = Method::Cast(value);
72            name_ = method->GetMethodName();
73            id_ = method->GetMethodId();
74            codesize_ = method->GetCodeSize();
75            hotness_ = hotness;
76        }
77
78        EntityId GetId() const
79        {
80            return id_;
81        }
82
83        void SetProfileBytecodeTime(float time)
84        {
85            profileBytecodeTime_.push_back(time);
86        }
87
88        void Print() const
89        {
90            float totalTime = std::accumulate(profileBytecodeTime_.begin(), profileBytecodeTime_.end(), 0.0);
91            float count = profileBytecodeTime_.size();
92#if defined(PANDA_TARGET_OHOS) && !defined(STANDALONE_MODE)
93            if (totalTime < MIN_PRINT_TIME && count < MIN_PRINT_COUNT) {
94                return;
95            }
96#endif
97            if (count <= 0) {
98                return;
99            }
100            float avgProfileBytecodeTime = totalTime / count;
101            LOG_TRACE(INFO) << std::left << std::setw(NAME_WIDTH) << name_ << std::right << std::setw(WIDTH) << id_
102                            << std::setw(WIDTH) << codesize_ << std::setw(WIDTH) << hotness_ << std::setw(WIDTH)
103                            << count << std::fixed << std::setprecision(PRECISION) << std::setw(WIDTH)
104                            << avgProfileBytecodeTime << std::setw(WIDTH) << totalTime;
105        }
106
107        void SetHotness(bool hotness)
108        {
109            hotness_ = hotness;
110        }
111
112    private:
113        const char* name_;
114        EntityId id_;
115        uint32_t codesize_;
116        std::list<float> profileBytecodeTime_;
117        bool hotness_;
118    };
119
120    MethodData* GetMethodData(EntityId id)
121    {
122        auto iter = methods.find(id);
123        if (iter == methods.end()) {
124            return nullptr;
125        }
126        return &(iter->second);
127    }
128
129    MethodData* TryGetMethodData(JSTaggedValue value, bool hotness = false)
130    {
131        MethodData method(value, hotness);
132        auto data = GetMethodData(method.GetId());
133        if (data) {
134            data->SetHotness(hotness);
135            return data;
136        } else {
137            auto res = methods.emplace(method.GetId(), method);
138            return &(res.first->second);
139        }
140    }
141
142    void Print() const
143    {
144        LOG_TRACE(INFO) << "only print methods which (total time > 10 ms || count > 10) on ohos device";
145        LOG_TRACE(INFO) << Title();
146        LOG_TRACE(INFO) << std::setw(NAME_WIDTH + WIDTH * (TOTAL_COLUMN - PROFILE_BYTECODE_TIME_COLUMN)) << ""
147                        << std::right << std::setw(WIDTH * PROFILE_BYTECODE_TIME_COLUMN) << "Profile Bytecode Time(ms)";
148        LOG_TRACE(INFO) << std::left << std::setw(NAME_WIDTH) << "Name" << std::right << std::setw(WIDTH) << "Id"
149                        << std::setw(WIDTH) << "CodeSize" << std::setw(WIDTH) << "Hotness" << std::setw(WIDTH)
150                        << "Count" << std::setw(WIDTH) << "Avg(ms)" << std::setw(WIDTH) << "Total(ms)";
151        for (auto& [id, method]: methods) {
152            method.Print();
153        }
154        LOG_TRACE(INFO) << Separator();
155        LOG_TRACE(INFO) << std::left << std::setw(NAME_WIDTH) << "MergeBeforeSave(ms) " << mergeTime_;
156        LOG_TRACE(INFO) << std::left << std::setw(NAME_WIDTH) << "MergeWithExistAP(ms) " << mergeWithExistProfileTime_;
157        LOG_TRACE(INFO) << std::left << std::setw(NAME_WIDTH) << "SaveAndRename(ms) " << saveTime_;
158        LOG_TRACE(INFO) << Separator();
159    }
160
161    void SetMergeTime(float time)
162    {
163        mergeTime_ = time;
164    }
165
166    void SetSaveTime(float time)
167    {
168        saveTime_ = time;
169    }
170
171    void SetMergeWithExistProfileTime(float time)
172    {
173        mergeWithExistProfileTime_ = time;
174    }
175
176private:
177    std::unordered_map<EntityId, MethodData> methods;
178    float mergeTime_;
179    float mergeWithExistProfileTime_;
180    float saveTime_;
181    bool enable_ {false};
182};
183} // namespace panda::ecmascript::pgo
184#endif // ECMASCRIPT_PGO_PROFILER_PGO_TRACE_H