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
30namespace proxima {
31namespace be {
32
33//! Predefined class
34class Profiler;
35//! Alias ProfilerPtr
36using ProfilerPtr = std::shared_ptr<Profiler>;
37
38//! Profiler collecting all the latency and other information during query
39class 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
163class 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