1 /* Copyright 2016 The TensorFlow Authors All Rights Reserved.
2
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 "tensorflow/core/profiler/internal/tfprof_utils.h"
17
18 #include <stdio.h>
19 #include <algorithm>
20 #include <memory>
21 #include <set>
22
23 #include "tensorflow/core/lib/strings/numbers.h"
24 #include "tensorflow/core/lib/strings/str_util.h"
25 #include "tensorflow/core/lib/strings/strcat.h"
26 #include "tensorflow/core/lib/strings/stringprintf.h"
27 #include "tensorflow/core/platform/protobuf.h"
28 #include "tensorflow/core/platform/regexp.h"
29
30 namespace tensorflow {
31 namespace tfprof {
FormatNumber(int64 n)32 string FormatNumber(int64 n) {
33 if (n < 1000) {
34 return strings::Printf("%lld", n);
35 } else if (n < 1000000) {
36 return strings::Printf("%.2fk", n / 1000.0);
37 } else if (n < 1000000000) {
38 return strings::Printf("%.2fm", n / 1000000.0);
39 } else {
40 return strings::Printf("%.2fb", n / 1000000000.0);
41 }
42 }
43
FormatTime(int64 micros)44 string FormatTime(int64 micros) {
45 if (micros < 1000) {
46 return strings::Printf("%lldus", micros);
47 } else if (micros < 1000000) {
48 return strings::Printf("%.2fms", micros / 1000.0);
49 } else {
50 return strings::Printf("%.2fsec", micros / 1000000.0);
51 }
52 }
53
FormatMemory(int64 bytes)54 string FormatMemory(int64 bytes) {
55 if (bytes < 1000) {
56 return strings::Printf("%lldB", bytes);
57 } else if (bytes < 1000000) {
58 return strings::Printf("%.2fKB", bytes / 1000.0);
59 } else {
60 return strings::Printf("%.2fMB", bytes / 1000000.0);
61 }
62 }
63
FormatShapes(const std::vector<int64> & shape)64 string FormatShapes(const std::vector<int64>& shape) {
65 return str_util::Join(shape, "x");
66 }
67
StringReplace(const string & str,const string & oldsub,const string & newsub)68 string StringReplace(const string& str, const string& oldsub,
69 const string& newsub) {
70 string out = str;
71 RE2::GlobalReplace(&out, oldsub, newsub);
72 return out;
73 }
74
75 namespace {
StripQuote(const string & s)76 string StripQuote(const string& s) {
77 int start = s.find_first_not_of("\"\'");
78 int end = s.find_last_not_of("\"\'");
79 if (start == s.npos || end == s.npos) return "";
80
81 return s.substr(start, end - start + 1);
82 }
83
ReturnError(const std::vector<string> & pieces,int idx)84 tensorflow::Status ReturnError(const std::vector<string>& pieces, int idx) {
85 string val;
86 if (pieces.size() > idx + 1) {
87 val = pieces[idx + 1];
88 }
89 return tensorflow::Status(
90 tensorflow::error::INVALID_ARGUMENT,
91 strings::StrCat("Invalid option '", pieces[idx], "' value: '", val, "'"));
92 }
93
CaseEqual(StringPiece s1,StringPiece s2)94 bool CaseEqual(StringPiece s1, StringPiece s2) {
95 if (s1.size() != s2.size()) return false;
96 return str_util::Lowercase(s1) == str_util::Lowercase(s2);
97 }
98
StringToBool(StringPiece str,bool * value)99 bool StringToBool(StringPiece str, bool* value) {
100 CHECK(value != nullptr) << "NULL output boolean given.";
101 if (CaseEqual(str, "true") || CaseEqual(str, "t") || CaseEqual(str, "yes") ||
102 CaseEqual(str, "y") || CaseEqual(str, "1")) {
103 *value = true;
104 return true;
105 }
106 if (CaseEqual(str, "false") || CaseEqual(str, "f") || CaseEqual(str, "no") ||
107 CaseEqual(str, "n") || CaseEqual(str, "0")) {
108 *value = false;
109 return true;
110 }
111 return false;
112 }
113 } // namespace
114
ParseCmdLine(const string & line,string * cmd,tensorflow::tfprof::Options * opts)115 tensorflow::Status ParseCmdLine(const string& line, string* cmd,
116 tensorflow::tfprof::Options* opts) {
117 std::vector<string> pieces =
118 str_util::Split(line, ' ', str_util::SkipEmpty());
119
120 std::vector<string> cmds_str(kCmds, kCmds + sizeof(kCmds) / sizeof(*kCmds));
121 if (std::find(cmds_str.begin(), cmds_str.end(), pieces[0]) ==
122 cmds_str.end()) {
123 return tensorflow::Status(tensorflow::error::INVALID_ARGUMENT,
124 "First string must be a valid command.");
125 }
126 *cmd = pieces[0];
127
128 for (int i = 1; i < pieces.size(); ++i) {
129 if (pieces[i] == string(tensorflow::tfprof::kOptions[0])) {
130 if (pieces.size() <= i + 1 ||
131 !strings::safe_strto32(pieces[i + 1], &opts->max_depth)) {
132 return ReturnError(pieces, i);
133 }
134 ++i;
135 } else if (pieces[i] == tensorflow::tfprof::kOptions[1]) {
136 if (pieces.size() <= i + 1 ||
137 !strings::safe_strto64(pieces[i + 1], &opts->min_bytes)) {
138 return ReturnError(pieces, i);
139 }
140 ++i;
141 } else if (pieces[i] == tensorflow::tfprof::kOptions[2]) {
142 if (pieces.size() <= i + 1 ||
143 !strings::safe_strto64(pieces[i + 1], &opts->min_peak_bytes)) {
144 return ReturnError(pieces, i);
145 }
146 ++i;
147 } else if (pieces[i] == tensorflow::tfprof::kOptions[3]) {
148 if (pieces.size() <= i + 1 ||
149 !strings::safe_strto64(pieces[i + 1], &opts->min_residual_bytes)) {
150 return ReturnError(pieces, i);
151 }
152 ++i;
153 } else if (pieces[i] == tensorflow::tfprof::kOptions[4]) {
154 if (pieces.size() <= i + 1 ||
155 !strings::safe_strto64(pieces[i + 1], &opts->min_output_bytes)) {
156 return ReturnError(pieces, i);
157 }
158 ++i;
159 } else if (pieces[i] == tensorflow::tfprof::kOptions[5]) {
160 if (pieces.size() <= i + 1 ||
161 !strings::safe_strto64(pieces[i + 1], &opts->min_micros)) {
162 return ReturnError(pieces, i);
163 }
164 ++i;
165 } else if (pieces[i] == tensorflow::tfprof::kOptions[6]) {
166 if (pieces.size() <= i + 1 ||
167 !strings::safe_strto64(pieces[i + 1],
168 &opts->min_accelerator_micros)) {
169 return ReturnError(pieces, i);
170 }
171 ++i;
172 } else if (pieces[i] == tensorflow::tfprof::kOptions[7]) {
173 if (pieces.size() <= i + 1 ||
174 !strings::safe_strto64(pieces[i + 1], &opts->min_cpu_micros)) {
175 return ReturnError(pieces, i);
176 }
177 ++i;
178 } else if (pieces[i] == tensorflow::tfprof::kOptions[8]) {
179 if (pieces.size() <= i + 1 ||
180 !strings::safe_strto64(pieces[i + 1], &opts->min_params)) {
181 return ReturnError(pieces, i);
182 }
183 ++i;
184 } else if (pieces[i] == tensorflow::tfprof::kOptions[9]) {
185 if (pieces.size() <= i + 1 ||
186 !strings::safe_strto64(pieces[i + 1], &opts->min_float_ops)) {
187 return ReturnError(pieces, i);
188 }
189 ++i;
190 } else if (pieces[i] == tensorflow::tfprof::kOptions[10]) {
191 if (pieces.size() <= i + 1 ||
192 !strings::safe_strto64(pieces[i + 1], &opts->min_occurrence)) {
193 return ReturnError(pieces, i);
194 }
195 ++i;
196 } else if (pieces[i] == tensorflow::tfprof::kOptions[11]) {
197 if (pieces.size() <= i + 1 ||
198 !strings::safe_strto64(pieces[i + 1], &opts->step)) {
199 return ReturnError(pieces, i);
200 }
201 ++i;
202 } else if (pieces[i] == tensorflow::tfprof::kOptions[12]) {
203 if (pieces.size() <= i + 1) {
204 return ReturnError(pieces, i);
205 }
206 std::set<string> order_by_set(
207 kOrderBy, kOrderBy + sizeof(kOrderBy) / sizeof(*kOrderBy));
208 auto order_by = order_by_set.find(pieces[i + 1]);
209 if (order_by == order_by_set.end()) {
210 return ReturnError(pieces, i);
211 }
212 opts->order_by = *order_by;
213 ++i;
214 } else if (pieces[i] == tensorflow::tfprof::kOptions[13]) {
215 if (pieces.size() <= i + 1) {
216 return ReturnError(pieces, i);
217 }
218 opts->account_type_regexes = str_util::Split(StripQuote(pieces[i + 1]),
219 ',', str_util::SkipEmpty());
220 ++i;
221 } else if (pieces[i] == tensorflow::tfprof::kOptions[14]) {
222 if (pieces.size() <= i + 1) {
223 return ReturnError(pieces, i);
224 }
225 opts->start_name_regexes = str_util::Split(StripQuote(pieces[i + 1]), ',',
226 str_util::SkipEmpty());
227 ++i;
228 } else if (pieces[i] == tensorflow::tfprof::kOptions[15]) {
229 if (pieces.size() <= i + 1) {
230 return ReturnError(pieces, i);
231 }
232 opts->trim_name_regexes = str_util::Split(StripQuote(pieces[i + 1]), ',',
233 str_util::SkipEmpty());
234 ++i;
235 } else if (pieces[i] == tensorflow::tfprof::kOptions[16]) {
236 if (pieces.size() <= i + 1) {
237 return ReturnError(pieces, i);
238 }
239 opts->show_name_regexes = str_util::Split(StripQuote(pieces[i + 1]), ',',
240 str_util::SkipEmpty());
241 ++i;
242 } else if (pieces[i] == tensorflow::tfprof::kOptions[17]) {
243 if (pieces.size() <= i + 1) {
244 return ReturnError(pieces, i);
245 }
246 opts->hide_name_regexes = str_util::Split(StripQuote(pieces[i + 1]), ',',
247 str_util::SkipEmpty());
248 ++i;
249 } else if (pieces[i] == tensorflow::tfprof::kOptions[18]) {
250 if ((pieces.size() > i + 1 && pieces[i + 1].find("-") == 0) ||
251 pieces.size() == i + 1) {
252 opts->account_displayed_op_only = true;
253 } else if (!StringToBool(pieces[i + 1],
254 &opts->account_displayed_op_only)) {
255 return ReturnError(pieces, i);
256 } else {
257 ++i;
258 }
259 } else if (pieces[i] == tensorflow::tfprof::kOptions[19]) {
260 if (pieces.size() <= i + 1) {
261 return ReturnError(pieces, i);
262 }
263 std::set<string> shown_set(kShown,
264 kShown + sizeof(kShown) / sizeof(*kShown));
265 std::vector<string> requested_vector = str_util::Split(
266 StripQuote(pieces[i + 1]), ',', str_util::SkipEmpty());
267 std::set<string> requested_set(requested_vector.begin(),
268 requested_vector.end());
269 for (const string& requested : requested_set) {
270 if (shown_set.find(requested) == shown_set.end()) {
271 return ReturnError(pieces, i);
272 }
273 }
274 opts->select = requested_set;
275 ++i;
276 } else if (pieces[i] == tensorflow::tfprof::kOptions[20]) {
277 if (pieces.size() <= i + 1) {
278 return ReturnError(pieces, i);
279 }
280
281 tensorflow::Status s =
282 ParseOutput(pieces[i + 1], &opts->output_type, &opts->output_options);
283 if (!s.ok()) return s;
284 ++i;
285 } else {
286 return ReturnError(pieces, i);
287 }
288 }
289 return tensorflow::Status::OK();
290 }
291
PrintHelp()292 void PrintHelp() {
293 printf(
294 "See https://github.com/tensorflow/tensorflow/tree/master/tensorflow/core/profiler/"
295 "README.md for profiler tutorial.\n");
296 printf(
297 "See https://github.com/tensorflow/tensorflow/tree/master/tensorflow/core/profiler/"
298 "g3doc/command_line.md for command line tool tutorial.\n");
299 printf(
300 "profiler --profile_path=<ProfileProto binary file> # required\n"
301 "\nOr:\n\n"
302 "profiler --graph_path=<GraphDef proto file> "
303 "# Contains model graph info (no needed for eager execution)\n"
304 " --run_meta_path=<RunMetadata proto file> "
305 "# Contains runtime info. Optional.\n"
306 " --run_log_path=<OpLogProto proto file> "
307 "# Contains extra source code, flops, custom type info. Optional\n\n");
308 printf(
309 "\nTo skip interactive mode, append one of the following commands:\n"
310 " scope: Organize profiles based on name scopes.\n"
311 " graph: Organize profiles based on graph node input/output.\n"
312 " op: Organize profiles based on operation type.\n"
313 " code: Organize profiles based on python codes (need op_log_path).\n"
314 " advise: Auto-profile and advise. (experimental)\n"
315 " set: Set options that will be default for follow up commands.\n"
316 " help: Show helps.\n");
317 fflush(stdout);
318 }
319
320 static const char* const kTotalMicrosHelp =
321 "total execution time: Sum of accelerator execution time and cpu execution "
322 "time.";
323 static const char* const kAccMicrosHelp =
324 "accelerator execution time: Time spent executing on the accelerator. "
325 "This is normally measured by the actual hardware library.";
326 static const char* const kCPUHelp =
327 "cpu execution time: The time from the start to the end of the operation. "
328 "It's the sum of actual cpu run time plus the time that it spends waiting "
329 "if part of computation is launched asynchronously.";
330 static const char* const kBytes =
331 "requested bytes: The memory requested by the operation, accumulatively.";
332 static const char* const kPeakBytes =
333 "peak bytes: The peak amount of memory that the operation is holding at "
334 "some point.";
335 static const char* const kResidualBytes =
336 "residual bytes: The memory not de-allocated after the operation finishes.";
337 static const char* const kOutputBytes =
338 "output bytes: The memory that is output from the operation (not "
339 "necessarilty allocated by the operation)";
340 static const char* const kOccurrence =
341 "occurrence: The number of times it occurs";
342 static const char* const kInputShapes =
343 "input shape: The shape of input tensors";
344 static const char* const kDevice = "device: which device is placed on.";
345 static const char* const kFloatOps =
346 "flops: Number of float operations. Note: Please read the implementation "
347 "for the math behind it.";
348 static const char* const kParams =
349 "param: Number of parameters (in the Variable).";
350 static const char* const kTensorValue = "tensor_value: Not supported now.";
351 static const char* const kOpTypes =
352 "op_types: The attributes of the operation, includes the Kernel name "
353 "device placed on and user-defined strings.";
354
355 static const char* const kScope =
356 "scope: The nodes in the model graph are organized by their names, which "
357 "is hierarchical like filesystem.";
358 static const char* const kCode =
359 "code: When python trace is available, the nodes are python lines and "
360 "their are organized by the python call stack.";
361 static const char* const kOp =
362 "op: The nodes are operation kernel type, such as MatMul, Conv2D. Graph "
363 "nodes belonging to the same type are aggregated together.";
364 static const char* const kAdvise =
365 "advise: Automatically profile and discover issues. (Experimental)";
366 static const char* const kSet =
367 "set: Set a value for an option for future use.";
368 static const char* const kHelp = "help: Print helping messages.";
369
QueryDoc(const string & cmd,const Options & opts)370 string QueryDoc(const string& cmd, const Options& opts) {
371 string cmd_help = "";
372 if (cmd == kCmds[0]) {
373 cmd_help = kScope;
374 } else if (cmd == kCmds[1]) {
375 cmd_help = kScope;
376 } else if (cmd == kCmds[2]) {
377 cmd_help = kCode;
378 } else if (cmd == kCmds[3]) {
379 cmd_help = kOp;
380 } else if (cmd == kCmds[4]) {
381 cmd_help = kAdvise;
382 } else if (cmd == kCmds[5]) {
383 cmd_help = kSet;
384 } else if (cmd == kCmds[6]) {
385 cmd_help = kHelp;
386 } else {
387 cmd_help = "Unknown command: " + cmd;
388 }
389
390 std::vector<string> helps;
391 for (const string& s : opts.select) {
392 if (s == kShown[0]) {
393 helps.push_back(kBytes);
394 } else if (s == kShown[1]) {
395 helps.push_back(strings::StrCat(kTotalMicrosHelp, "\n", kCPUHelp, "\n",
396 kAccMicrosHelp));
397 } else if (s == kShown[2]) {
398 helps.push_back(kParams);
399 } else if (s == kShown[3]) {
400 helps.push_back(kFloatOps);
401 } else if (s == kShown[4]) {
402 helps.push_back(kTensorValue);
403 } else if (s == kShown[5]) {
404 helps.push_back(kDevice);
405 } else if (s == kShown[6]) {
406 helps.push_back(kOpTypes);
407 } else if (s == kShown[7]) {
408 helps.push_back(kOccurrence);
409 } else if (s == kShown[8]) {
410 helps.push_back(kInputShapes);
411 } else if (s == kShown[9]) {
412 helps.push_back(kAccMicrosHelp);
413 } else if (s == kShown[10]) {
414 helps.push_back(kCPUHelp);
415 } else if (s == kShown[11]) {
416 helps.push_back(kPeakBytes);
417 } else if (s == kShown[12]) {
418 helps.push_back(kResidualBytes);
419 } else if (s == kShown[13]) {
420 helps.push_back(kOutputBytes);
421 } else {
422 helps.push_back("Unknown select: " + s);
423 }
424 }
425 return strings::StrCat("\nDoc:\n", cmd_help, "\n",
426 str_util::Join(helps, "\n"), "\n\n");
427 }
428
429 } // namespace tfprof
430 } // namespace tensorflow
431