be/src/util/pprof_utils.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #include "util/pprof_utils.h" |
19 | | |
20 | | #include <stdio.h> |
21 | | #include <unistd.h> |
22 | | |
23 | | #include <cstdlib> |
24 | | #include <fstream> // IWYU pragma: keep |
25 | | #include <memory> |
26 | | |
27 | | #include "absl/strings/substitute.h" |
28 | | #include "agent/utils.h" |
29 | | #include "io/fs/local_file_system.h" |
30 | | |
31 | | namespace doris { |
32 | | namespace config { |
33 | | extern std::string pprof_profile_dir; |
34 | | } |
35 | | |
36 | 0 | Status PprofUtils::get_pprof_cmd(std::string* cmd) { |
37 | 0 | AgentUtils util; |
38 | | // check if pprof cmd exist |
39 | 0 | const static std::string tools_path = std::string(std::getenv("DORIS_HOME")) + "/tools/bin/"; |
40 | 0 | std::string pprof_cmd = tools_path + "pprof"; |
41 | 0 | std::string msg; |
42 | 0 | bool rc = util.exec_cmd(pprof_cmd + " --version", &msg); |
43 | 0 | if (!rc) { |
44 | | // not found in BE tools dir, found in system |
45 | 0 | pprof_cmd = "pprof"; |
46 | 0 | rc = util.exec_cmd(pprof_cmd + " --version", &msg); |
47 | 0 | if (!rc) { |
48 | 0 | return Status::NotSupported( |
49 | 0 | "pprof: command not found in systemp PATH or be/tools/bin/. Install gperftools " |
50 | 0 | "first."); |
51 | 0 | } |
52 | 0 | } |
53 | 0 | *cmd = pprof_cmd; |
54 | 0 | return Status::OK(); |
55 | 0 | } |
56 | | |
57 | 0 | Status PprofUtils::get_perf_cmd(std::string* cmd) { |
58 | 0 | AgentUtils util; |
59 | | // check if perf cmd exist |
60 | 0 | std::string perf_cmd = "perf"; |
61 | 0 | std::string msg; |
62 | 0 | bool rc = util.exec_cmd(perf_cmd + " --version", &msg); |
63 | 0 | if (!rc) { |
64 | 0 | return Status::NotSupported("perf: command not found in systemp PATH"); |
65 | 0 | } |
66 | 0 | *cmd = perf_cmd; |
67 | 0 | return Status::OK(); |
68 | 0 | } |
69 | | |
70 | 0 | Status PprofUtils::get_self_cmdline(std::string* cmd) { |
71 | | // get cmdline |
72 | 0 | FILE* fp = fopen("/proc/self/cmdline", "r"); |
73 | 0 | if (fp == nullptr) { |
74 | 0 | return Status::InternalError("Unable to open file: /proc/self/cmdline"); |
75 | 0 | } |
76 | 0 | char buf[1024]; |
77 | |
|
78 | 0 | Status res = Status::OK(); |
79 | |
|
80 | 0 | if (fscanf(fp, "%1023s ", buf) != 1) { |
81 | 0 | res = Status::InternalError("get_self_cmdline read buffer failed"); |
82 | 0 | } |
83 | 0 | fclose(fp); |
84 | 0 | *cmd = buf; |
85 | 0 | return res; |
86 | 0 | } |
87 | | |
88 | | Status PprofUtils::get_readable_profile(const std::string& file_or_content, bool is_file, |
89 | 0 | std::stringstream* output) { |
90 | | // get pprof cmd |
91 | 0 | std::string pprof_cmd; |
92 | 0 | RETURN_IF_ERROR(PprofUtils::get_pprof_cmd(&pprof_cmd)); |
93 | | |
94 | | // get self cmdline |
95 | 0 | std::string self_cmdline; |
96 | 0 | RETURN_IF_ERROR(PprofUtils::get_self_cmdline(&self_cmdline)); |
97 | | |
98 | | // save file if necessary |
99 | 0 | std::string final_file; |
100 | 0 | if (!is_file) { |
101 | 0 | std::stringstream tmp_file; |
102 | 0 | tmp_file << config::pprof_profile_dir << "/pprof_profile." << getpid() << "." << rand(); |
103 | 0 | std::ofstream outfile; |
104 | 0 | outfile.open(tmp_file.str().c_str()); |
105 | 0 | outfile << file_or_content; |
106 | 0 | outfile.close(); |
107 | 0 | final_file = tmp_file.str(); |
108 | 0 | } else { |
109 | 0 | final_file = file_or_content; |
110 | 0 | } |
111 | | |
112 | | // parse raw with "pprof --text cmdline raw_file" |
113 | 0 | std::string cmd_output; |
114 | 0 | std::string final_cmd = pprof_cmd + absl::Substitute(" --text $0 $1", self_cmdline, final_file); |
115 | 0 | AgentUtils util; |
116 | 0 | LOG(INFO) << "begin to run command: " << final_cmd; |
117 | 0 | bool rc = util.exec_cmd(final_cmd, &cmd_output, false); |
118 | | |
119 | | // delete raw file |
120 | 0 | static_cast<void>(io::global_local_filesystem()->delete_file(file_or_content)); |
121 | |
|
122 | 0 | if (!rc) { |
123 | 0 | return Status::InternalError("Failed to execute command: {}", cmd_output); |
124 | 0 | } |
125 | | |
126 | 0 | (*output) << "Profile(Sample 30 seconds)" << std::endl; |
127 | 0 | (*output) << cmd_output << std::endl; |
128 | 0 | return Status::OK(); |
129 | 0 | } |
130 | | |
131 | | Status PprofUtils::generate_flamegraph(int32_t sample_seconds, |
132 | | const std::string& flame_graph_tool_dir, bool return_file, |
133 | 0 | std::string* svg_file_or_content) { |
134 | | // get perf cmd |
135 | 0 | std::string perf_cmd; |
136 | 0 | RETURN_IF_ERROR(PprofUtils::get_perf_cmd(&perf_cmd)); |
137 | | |
138 | | // check if FlameGraph has been installed |
139 | | // check stackcollapse-perf.pl and flamegraph.pl exist |
140 | 0 | std::string stackcollapse_perf_pl = flame_graph_tool_dir + "/stackcollapse-perf.pl"; |
141 | 0 | std::string flamegraph_pl = flame_graph_tool_dir + "/flamegraph.pl"; |
142 | 0 | bool exists = false; |
143 | 0 | RETURN_IF_ERROR(io::global_local_filesystem()->exists(stackcollapse_perf_pl, &exists)); |
144 | 0 | RETURN_IF_ERROR(io::global_local_filesystem()->exists(flamegraph_pl, &exists)); |
145 | 0 | if (!exists) { |
146 | 0 | return Status::InternalError( |
147 | 0 | "Missing stackcollapse-perf.pl or flamegraph.pl in FlameGraph"); |
148 | 0 | } |
149 | | |
150 | | // tmp output profile file |
151 | 0 | std::stringstream tmp_file; |
152 | 0 | tmp_file << config::pprof_profile_dir << "/cpu_perf." << getpid() << "." << rand(); |
153 | | |
154 | | // sample |
155 | 0 | std::stringstream cmd; |
156 | 0 | cmd << perf_cmd << " record -m 2 -g -p " << getpid() << " -o " << tmp_file.str() << " -- sleep " |
157 | 0 | << sample_seconds; |
158 | |
|
159 | 0 | AgentUtils util; |
160 | 0 | std::string cmd_output; |
161 | 0 | LOG(INFO) << "begin to run command: " << cmd.str(); |
162 | 0 | bool rc = util.exec_cmd(cmd.str(), &cmd_output); |
163 | 0 | if (!rc) { |
164 | 0 | static_cast<void>(io::global_local_filesystem()->delete_file(tmp_file.str())); |
165 | 0 | return Status::InternalError("Failed to execute perf command: {}", cmd_output); |
166 | 0 | } |
167 | | |
168 | | // generate flamegraph |
169 | | |
170 | 0 | std::string res_content; |
171 | 0 | if (return_file) { |
172 | 0 | std::stringstream graph_file; |
173 | 0 | graph_file << config::pprof_profile_dir << "/flamegraph." << getpid() << "." << rand() |
174 | 0 | << ".svg"; |
175 | 0 | std::stringstream gen_cmd; |
176 | 0 | gen_cmd << perf_cmd << " script -i " << tmp_file.str() << " | " << stackcollapse_perf_pl |
177 | 0 | << " | " << flamegraph_pl << " > " << graph_file.str(); |
178 | 0 | LOG(INFO) << "begin to run command: " << gen_cmd.str(); |
179 | 0 | rc = util.exec_cmd(gen_cmd.str(), &res_content); |
180 | 0 | if (!rc) { |
181 | 0 | static_cast<void>(io::global_local_filesystem()->delete_file(tmp_file.str())); |
182 | 0 | static_cast<void>(io::global_local_filesystem()->delete_file(graph_file.str())); |
183 | 0 | return Status::InternalError("Failed to execute perf script command: {}", res_content); |
184 | 0 | } |
185 | 0 | *svg_file_or_content = graph_file.str(); |
186 | 0 | } else { |
187 | 0 | std::stringstream gen_cmd; |
188 | 0 | gen_cmd << perf_cmd << " script -i " << tmp_file.str() << " | " << stackcollapse_perf_pl |
189 | 0 | << " | " << flamegraph_pl; |
190 | 0 | LOG(INFO) << "begin to run command: " << gen_cmd.str(); |
191 | 0 | rc = util.exec_cmd(gen_cmd.str(), &res_content, false); |
192 | 0 | if (!rc) { |
193 | 0 | static_cast<void>(io::global_local_filesystem()->delete_file(tmp_file.str())); |
194 | 0 | return Status::InternalError("Failed to execute perf script command: {}", res_content); |
195 | 0 | } |
196 | 0 | *svg_file_or_content = res_content; |
197 | 0 | } |
198 | 0 | return Status::OK(); |
199 | 0 | } |
200 | | |
201 | | } // namespace doris |