1 | /** |
2 | * Copyright 2021 Alibaba, Inc. and its affiliates. All Rights Reserved. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | * |
16 | * \author guonix |
17 | * \date Dec 2020 |
18 | * \brief |
19 | */ |
20 | |
21 | #pragma once |
22 | |
23 | #include <memory> |
24 | #include <vector> |
25 | #include <ailego/encoding/json.h> |
26 | #include <ailego/utility/time_helper.h> |
27 | #include "error_code.h" |
28 | #include "logger.h" |
29 | |
30 | namespace proxima { |
31 | namespace be { |
32 | |
33 | //! Predefined class |
34 | class Profiler; |
35 | //! Alias ProfilerPtr |
36 | using ProfilerPtr = std::shared_ptr<Profiler>; |
37 | |
38 | //! Profiler collecting all the latency and other information during query |
39 | class Profiler { |
40 | private: |
41 | //! Stage object |
42 | struct Stage { |
43 | //! Constructor |
44 | explicit Stage(ailego::JsonObject *node) : node_(node) {} |
45 | //! Stage node, which stored in JsonTree held by Profiler |
46 | ailego::JsonObject *node_{nullptr}; |
47 | //! Stage latency, started when creating Stage object |
48 | ailego::ElapsedTime latency_; |
49 | }; |
50 | |
51 | public: |
52 | //! Constructor |
53 | explicit Profiler(bool enable = false) : enable_(enable) { |
54 | if (enabled()) { |
55 | root_.assign(ailego::JsonObject()); |
56 | } |
57 | } |
58 | |
59 | //! Check enabled |
60 | bool enabled() const { |
61 | return enable_; |
62 | } |
63 | |
64 | //! Start profiler |
65 | void start() { |
66 | if (enabled() && path_.empty()) { |
67 | path_.emplace_back(Stage(&root_.as_object())); |
68 | } |
69 | } |
70 | |
71 | //! Stop profiler |
72 | void stop() { |
73 | if (enabled()) { |
74 | if (path_.size() == 1) { |
75 | // Root always held in path_[0] |
76 | close_stage(); |
77 | } else { |
78 | LOG_WARN("There are stages have not been closed, stages[%zu]" , |
79 | path_.size()); |
80 | // Manually set latency to root, which should not be normal way |
81 | root_["latency" ] = path_.begin()->latency_.micro_seconds(); |
82 | } |
83 | } |
84 | } |
85 | |
86 | //! Open stage, start timer of stage |
87 | int open_stage(const std::string &name) { |
88 | if (enabled()) { |
89 | if (path_.empty()) { |
90 | LOG_ERROR("Profiler did not start yet" ); |
91 | return PROXIMA_BE_ERROR_CODE(RuntimeError); |
92 | } |
93 | if (name.empty()) { |
94 | LOG_ERROR("Can't open stage with empty name" ); |
95 | return PROXIMA_BE_ERROR_CODE(RuntimeError); |
96 | } |
97 | ailego::JsonString key(name); |
98 | ailego::JsonObject child; |
99 | |
100 | current_path()->set(key, child); // add child |
101 | path_.emplace_back(Stage( |
102 | &((*current_path())[name.c_str()].as_object()))); // move to child |
103 | } |
104 | return 0; |
105 | } |
106 | |
107 | //! Close stage and stop timer of stage(represent by stage.latency) |
108 | int close_stage() { |
109 | if (enabled()) { |
110 | if (path_.empty()) { |
111 | LOG_ERROR("No available stage can be closed" ); |
112 | return PROXIMA_BE_ERROR_CODE(RuntimeError); |
113 | } |
114 | ailego::JsonValue latency(current()->latency_.micro_seconds()); |
115 | current_path()->set("latency" , latency); |
116 | path_.pop_back(); |
117 | } |
118 | return 0; |
119 | } |
120 | |
121 | //! add value to profiler |
122 | template <typename VALUE_TYPE> |
123 | int add(const std::string &name, const VALUE_TYPE &v) { |
124 | if (enabled()) { |
125 | if (path_.empty()) { |
126 | return PROXIMA_BE_ERROR_CODE(RuntimeError); |
127 | } |
128 | |
129 | ailego::JsonString key(name); |
130 | ailego::JsonValue value(v); |
131 | current_path()->set(key, value); |
132 | } |
133 | return 0; |
134 | } |
135 | |
136 | //! Serialize profiler to string(Json Format) |
137 | std::string as_json_string() const { |
138 | return enabled() ? root_.as_json_string().as_stl_string() |
139 | : std::string("{}" ); |
140 | } |
141 | |
142 | private: |
143 | Stage *current() { |
144 | return path_.rbegin().operator->(); |
145 | } |
146 | |
147 | ailego::JsonObject *current_path() { |
148 | return current()->node_; |
149 | } |
150 | |
151 | private: |
152 | //! enable flag |
153 | bool enable_{false}; |
154 | |
155 | //! root handler |
156 | ailego::JsonValue root_; |
157 | |
158 | //! Depth-First paths |
159 | std::vector<Stage> path_; |
160 | }; |
161 | |
162 | //! Helper for latency |
163 | class ScopedLatency { |
164 | public: |
165 | //! Constructor |
166 | explicit ScopedLatency(const char *name, ProfilerPtr profiler) |
167 | : name_(name), profiler_(std::move(profiler)) {} |
168 | |
169 | //! Destructor |
170 | ~ScopedLatency() { |
171 | profiler_->add(name_, latency_.micro_seconds()); |
172 | } |
173 | |
174 | private: |
175 | //! Name of latency |
176 | const char *name_{nullptr}; |
177 | |
178 | //! Timer handler |
179 | ailego::ElapsedTime latency_; |
180 | |
181 | //! Profiler handler |
182 | ProfilerPtr profiler_; |
183 | }; |
184 | |
185 | } // namespace be |
186 | } // namespace proxima |
187 | |