1 /* Copyright 2017 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/rpc/client/save_profile.h"
17 
18 #include <memory>
19 #include <sstream>
20 #include <string>
21 #include <vector>
22 
23 #include "absl/strings/match.h"
24 #include "absl/strings/str_cat.h"
25 #include "absl/strings/str_replace.h"
26 #include "absl/strings/string_view.h"
27 #include "absl/strings/strip.h"
28 #include "absl/time/clock.h"
29 #include "absl/time/time.h"
30 #include "tensorflow/core/lib/io/zlib_compression_options.h"
31 #include "tensorflow/core/lib/io/zlib_outputbuffer.h"
32 #include "tensorflow/core/platform/env.h"
33 #include "tensorflow/core/platform/errors.h"
34 #include "tensorflow/core/platform/file_system.h"
35 #include "tensorflow/core/platform/logging.h"
36 #include "tensorflow/core/platform/status.h"
37 #include "tensorflow/core/profiler/profiler_service.pb.h"
38 #include "tensorflow/core/profiler/utils/file_system_utils.h"
39 
40 // Windows.h #defines ERROR, but it is also used in
41 // tensorflow/core/util/event.proto
42 #undef ERROR
43 #include "tensorflow/core/util/events_writer.h"
44 
45 namespace tensorflow {
46 namespace profiler {
47 namespace {
48 
49 
50 constexpr char kProtoTraceFileName[] = "trace";
51 constexpr char kTfStatsHelperSuffix[] = "tf_stats_helper_result";
52 
DumpToolData(absl::string_view run_dir,absl::string_view host,const ProfileToolData & tool,std::ostream * os)53 Status DumpToolData(absl::string_view run_dir, absl::string_view host,
54                     const ProfileToolData& tool, std::ostream* os) {
55   // Don't save the intermediate results for combining the per host tool data.
56   if (absl::EndsWith(tool.name(), kTfStatsHelperSuffix)) return Status::OK();
57   std::string host_prefix = host.empty() ? "" : absl::StrCat(host, ".");
58   std::string path =
59       ProfilerJoinPath(run_dir, absl::StrCat(host_prefix, tool.name()));
60   TF_RETURN_IF_ERROR(WriteStringToFile(Env::Default(), path, tool.data()));
61   if (os) {
62     *os << "Dumped tool data for " << tool.name() << " to " << path
63         << std::endl;
64   }
65   return Status::OK();
66 }
67 
WriteGzippedDataToFile(const std::string & filepath,const std::string & data)68 Status WriteGzippedDataToFile(const std::string& filepath,
69                               const std::string& data) {
70   std::unique_ptr<WritableFile> file;
71   TF_RETURN_IF_ERROR(Env::Default()->NewWritableFile(filepath, &file));
72   io::ZlibCompressionOptions options = io::ZlibCompressionOptions::GZIP();
73   io::ZlibOutputBuffer buffer(file.get(), options.input_buffer_size,
74                               options.output_buffer_size, options);
75   TF_RETURN_IF_ERROR(buffer.Init());
76   TF_RETURN_IF_ERROR(buffer.Append(data));
77   TF_RETURN_IF_ERROR(buffer.Close());
78   TF_RETURN_IF_ERROR(file->Close());
79   return Status::OK();
80 }
81 
GetOrCreateRunDir(const std::string & repository_root,const std::string & run,std::string * run_dir,std::ostream * os)82 Status GetOrCreateRunDir(const std::string& repository_root,
83                          const std::string& run, std::string* run_dir,
84                          std::ostream* os) {
85   // Creates a directory to <repository_root>/<run>/.
86   *run_dir = ProfilerJoinPath(repository_root, run);
87   *os << "Creating directory: " << *run_dir;
88   TF_RETURN_IF_ERROR(Env::Default()->RecursivelyCreateDir(*run_dir));
89   return Status::OK();
90 }
91 }  // namespace
92 
GetTensorBoardProfilePluginDir(const std::string & logdir)93 std::string GetTensorBoardProfilePluginDir(const std::string& logdir) {
94   constexpr char kPluginName[] = "plugins";
95   constexpr char kProfileName[] = "profile";
96   return ProfilerJoinPath(logdir, kPluginName, kProfileName);
97 }
98 
MaybeCreateEmptyEventFile(const std::string & logdir)99 Status MaybeCreateEmptyEventFile(const std::string& logdir) {
100   // Suffix for an empty event file.  it should be kept in sync with
101   // _EVENT_FILE_SUFFIX in tensorflow/python/eager/profiler.py.
102   constexpr char kProfileEmptySuffix[] = ".profile-empty";
103   TF_RETURN_IF_ERROR(Env::Default()->RecursivelyCreateDir(logdir));
104 
105   std::vector<std::string> children;
106   TF_RETURN_IF_ERROR(Env::Default()->GetChildren(logdir, &children));
107   for (const std::string& child : children) {
108     if (absl::EndsWith(child, kProfileEmptySuffix)) {
109       return Status::OK();
110     }
111   }
112   EventsWriter event_writer(ProfilerJoinPath(logdir, "events"));
113   return event_writer.InitWithSuffix(kProfileEmptySuffix);
114 }
115 
SaveProfile(const std::string & repository_root,const std::string & run,const std::string & host,const ProfileResponse & response,std::ostream * os)116 Status SaveProfile(const std::string& repository_root, const std::string& run,
117                    const std::string& host, const ProfileResponse& response,
118                    std::ostream* os) {
119   if (response.tool_data().empty()) return Status::OK();
120   std::string run_dir;
121   TF_RETURN_IF_ERROR(GetOrCreateRunDir(repository_root, run, &run_dir, os));
122   // Windows file names do not support colons.
123   std::string hostname = absl::StrReplaceAll(host, {{":", "_"}});
124   for (const auto& tool_data : response.tool_data()) {
125     TF_RETURN_IF_ERROR(DumpToolData(run_dir, hostname, tool_data, os));
126   }
127   return Status::OK();
128 }
129 
SaveGzippedToolData(const std::string & repository_root,const std::string & run,const std::string & host,const std::string & tool_name,const std::string & data)130 Status SaveGzippedToolData(const std::string& repository_root,
131                            const std::string& run, const std::string& host,
132                            const std::string& tool_name,
133                            const std::string& data) {
134   std::string run_dir;
135   std::stringstream ss;
136   Status status = GetOrCreateRunDir(repository_root, run, &run_dir, &ss);
137   LOG(INFO) << ss.str();
138   TF_RETURN_IF_ERROR(status);
139   std::string host_prefix = host.empty() ? "" : absl::StrCat(host, ".");
140   std::string path =
141       ProfilerJoinPath(run_dir, absl::StrCat(host_prefix, tool_name));
142   TF_RETURN_IF_ERROR(WriteGzippedDataToFile(path, data));
143   LOG(INFO) << "Dumped gzipped tool data for " << tool_name << " to " << path;
144   return Status::OK();
145 }
146 
GetCurrentTimeStampAsString()147 std::string GetCurrentTimeStampAsString() {
148   return absl::FormatTime("%E4Y_%m_%d_%H_%M_%S", absl::Now(),
149                           absl::LocalTimeZone());
150 }
151 
152 }  // namespace profiler
153 }  // namespace tensorflow
154