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
20 #include <algorithm>
21 #include <memory>
22 #include <set>
23
24 #include "absl/strings/match.h"
25 #include "absl/strings/numbers.h"
26 #include "absl/strings/str_cat.h"
27 #include "absl/strings/str_format.h"
28 #include "absl/strings/str_split.h"
29 #include "tensorflow/core/platform/protobuf.h"
30 #include "tensorflow/core/platform/regexp.h"
31
32 namespace tensorflow {
33 namespace tfprof {
FormatNumber(int64 n)34 string FormatNumber(int64 n) {
35 if (n < 1000) {
36 return absl::StrFormat("%d", n);
37 } else if (n < 1000000) {
38 return absl::StrFormat("%.2fk", n / 1000.0);
39 } else if (n < 1000000000) {
40 return absl::StrFormat("%.2fm", n / 1000000.0);
41 } else {
42 return absl::StrFormat("%.2fb", n / 1000000000.0);
43 }
44 }
45
FormatTime(int64 micros)46 string FormatTime(int64 micros) {
47 if (micros < 1000) {
48 return absl::StrFormat("%dus", micros);
49 } else if (micros < 1000000) {
50 return absl::StrFormat("%.2fms", micros / 1000.0);
51 } else {
52 return absl::StrFormat("%.2fsec", micros / 1000000.0);
53 }
54 }
55
FormatMemory(int64 bytes)56 string FormatMemory(int64 bytes) {
57 if (bytes < 1000) {
58 return absl::StrFormat("%dB", bytes);
59 } else if (bytes < 1000000) {
60 return absl::StrFormat("%.2fKB", bytes / 1000.0);
61 } else {
62 return absl::StrFormat("%.2fMB", bytes / 1000000.0);
63 }
64 }
65
FormatShapes(const std::vector<int64> & shape)66 string FormatShapes(const std::vector<int64>& shape) {
67 return absl::StrJoin(shape, "x");
68 }
69
StringReplace(const string & str,const string & oldsub,const string & newsub)70 string StringReplace(const string& str, const string& oldsub,
71 const string& newsub) {
72 string out = str;
73 RE2::GlobalReplace(&out, oldsub, newsub);
74 return out;
75 }
76
77 namespace {
StripQuote(const string & s)78 string StripQuote(const string& s) {
79 int start = s.find_first_not_of("\"\'");
80 int end = s.find_last_not_of("\"\'");
81 if (start == s.npos || end == s.npos) return "";
82
83 return s.substr(start, end - start + 1);
84 }
85
ReturnError(const std::vector<string> & pieces,int idx)86 tensorflow::Status ReturnError(const std::vector<string>& pieces, int idx) {
87 string val;
88 if (pieces.size() > idx + 1) {
89 val = pieces[idx + 1];
90 }
91 return tensorflow::Status(
92 tensorflow::error::INVALID_ARGUMENT,
93 absl::StrCat("Invalid option '", pieces[idx], "' value: '", val, "'"));
94 }
95
CaseEqual(StringPiece s1,StringPiece s2)96 bool CaseEqual(StringPiece s1, StringPiece s2) {
97 if (s1.size() != s2.size()) return false;
98 return absl::AsciiStrToLower(s1) == absl::AsciiStrToLower(s2);
99 }
100
StringToBool(StringPiece str,bool * value)101 bool StringToBool(StringPiece str, bool* value) {
102 CHECK(value != nullptr) << "NULL output boolean given.";
103 if (CaseEqual(str, "true") || CaseEqual(str, "t") || CaseEqual(str, "yes") ||
104 CaseEqual(str, "y") || CaseEqual(str, "1")) {
105 *value = true;
106 return true;
107 }
108 if (CaseEqual(str, "false") || CaseEqual(str, "f") || CaseEqual(str, "no") ||
109 CaseEqual(str, "n") || CaseEqual(str, "0")) {
110 *value = false;
111 return true;
112 }
113 return false;
114 }
115 } // namespace
116
ParseCmdLine(const string & line,string * cmd,tensorflow::tfprof::Options * opts)117 tensorflow::Status ParseCmdLine(const string& line, string* cmd,
118 tensorflow::tfprof::Options* opts) {
119 std::vector<string> pieces = absl::StrSplit(line, ' ', absl::SkipEmpty());
120
121 std::vector<string> cmds_str(kCmds, kCmds + sizeof(kCmds) / sizeof(*kCmds));
122 if (std::find(cmds_str.begin(), cmds_str.end(), pieces[0]) ==
123 cmds_str.end()) {
124 return tensorflow::Status(tensorflow::error::INVALID_ARGUMENT,
125 "First string must be a valid command.");
126 }
127 *cmd = pieces[0];
128
129 for (int i = 1; i < pieces.size(); ++i) {
130 if (pieces[i] == string(tensorflow::tfprof::kOptions[0])) {
131 if (pieces.size() <= i + 1 ||
132 !absl::SimpleAtoi(pieces[i + 1], &opts->max_depth)) {
133 return ReturnError(pieces, i);
134 }
135 ++i;
136 } else if (pieces[i] == tensorflow::tfprof::kOptions[1]) {
137 if (pieces.size() <= i + 1 ||
138 !absl::SimpleAtoi(pieces[i + 1], &opts->min_bytes)) {
139 return ReturnError(pieces, i);
140 }
141 ++i;
142 } else if (pieces[i] == tensorflow::tfprof::kOptions[2]) {
143 if (pieces.size() <= i + 1 ||
144 !absl::SimpleAtoi(pieces[i + 1], &opts->min_peak_bytes)) {
145 return ReturnError(pieces, i);
146 }
147 ++i;
148 } else if (pieces[i] == tensorflow::tfprof::kOptions[3]) {
149 if (pieces.size() <= i + 1 ||
150 !absl::SimpleAtoi(pieces[i + 1], &opts->min_residual_bytes)) {
151 return ReturnError(pieces, i);
152 }
153 ++i;
154 } else if (pieces[i] == tensorflow::tfprof::kOptions[4]) {
155 if (pieces.size() <= i + 1 ||
156 !absl::SimpleAtoi(pieces[i + 1], &opts->min_output_bytes)) {
157 return ReturnError(pieces, i);
158 }
159 ++i;
160 } else if (pieces[i] == tensorflow::tfprof::kOptions[5]) {
161 if (pieces.size() <= i + 1 ||
162 !absl::SimpleAtoi(pieces[i + 1], &opts->min_micros)) {
163 return ReturnError(pieces, i);
164 }
165 ++i;
166 } else if (pieces[i] == tensorflow::tfprof::kOptions[6]) {
167 if (pieces.size() <= i + 1 ||
168 !absl::SimpleAtoi(pieces[i + 1], &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 !absl::SimpleAtoi(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 !absl::SimpleAtoi(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 !absl::SimpleAtoi(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 !absl::SimpleAtoi(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 !absl::SimpleAtoi(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 =
219 absl::StrSplit(StripQuote(pieces[i + 1]), ',', absl::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 =
226 absl::StrSplit(StripQuote(pieces[i + 1]), ',', absl::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 =
233 absl::StrSplit(StripQuote(pieces[i + 1]), ',', absl::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 =
240 absl::StrSplit(StripQuote(pieces[i + 1]), ',', absl::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 =
247 absl::StrSplit(StripQuote(pieces[i + 1]), ',', absl::SkipEmpty());
248 ++i;
249 } else if (pieces[i] == tensorflow::tfprof::kOptions[18]) {
250 if ((pieces.size() > i + 1 && absl::StartsWith(pieces[i + 1], "-")) ||
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 =
266 absl::StrSplit(StripQuote(pieces[i + 1]), ',', absl::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 absl::PrintF(
294 "See https://github.com/tensorflow/tensorflow/tree/master/tensorflow/core/profiler/"
295 "README.md for profiler tutorial.\n");
296 absl::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 absl::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 absl::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 "necessarily 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(
396 absl::StrCat(kTotalMicrosHelp, "\n", kCPUHelp, "\n", 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 absl::StrCat("\nDoc:\n", cmd_help, "\n", absl::StrJoin(helps, "\n"),
426 "\n\n");
427 }
428
429 } // namespace tfprof
430 } // namespace tensorflow
431