1 /* Copyright 2019 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 #include "tensorflow/core/profiler/utils/xplane_utils.h"
16
17 #include <algorithm>
18 #include <string>
19 #include <utility>
20 #include <vector>
21
22 #include "absl/container/flat_hash_map.h"
23 #include "absl/container/flat_hash_set.h"
24 #include "absl/strings/match.h"
25 #include "absl/strings/string_view.h"
26 #include "tensorflow/core/platform/logging.h"
27 #include "tensorflow/core/platform/protobuf.h"
28 #include "tensorflow/core/platform/types.h"
29 #include "tensorflow/core/profiler/protobuf/xplane.pb.h"
30 #include "tensorflow/core/profiler/utils/time_utils.h"
31 #include "tensorflow/core/profiler/utils/timespan.h"
32 #include "tensorflow/core/profiler/utils/xplane_builder.h"
33 #include "tensorflow/core/profiler/utils/xplane_visitor.h"
34
35 namespace tensorflow {
36 namespace profiler {
37 namespace {
38
39 // Returns the index of the first element in array for which pred is true.
40 // Returns -1 if no such element is found.
41 template <typename T, typename Pred>
Find(const protobuf::RepeatedPtrField<T> & array,const Pred & pred)42 int Find(const protobuf::RepeatedPtrField<T>& array, const Pred& pred) {
43 for (int i = 0; i < array.size(); ++i) {
44 if (pred(&array.Get(i))) return i;
45 }
46 return -1;
47 }
48
49 // Returns the indices of all elements in array for which pred is true.
50 template <typename T, typename Pred>
FindAll(const protobuf::RepeatedPtrField<T> & array,const Pred & pred)51 std::vector<int> FindAll(const protobuf::RepeatedPtrField<T>& array,
52 const Pred& pred) {
53 std::vector<int> indices;
54 for (int i = 0; i < array.size(); ++i) {
55 if (pred(&array.Get(i))) indices.push_back(i);
56 }
57 return indices;
58 }
59
60 template <typename T>
RemoveAt(protobuf::RepeatedPtrField<T> * array,const std::vector<int> & indices)61 void RemoveAt(protobuf::RepeatedPtrField<T>* array,
62 const std::vector<int>& indices) {
63 if (indices.empty()) return;
64 if (array->size() == indices.size()) {
65 // Assumes that 'indices' consists of [0 ... N-1].
66 array->Clear();
67 return;
68 }
69 auto remove_iter = indices.begin();
70 int i = *(remove_iter++);
71 for (int j = i + 1; j < array->size(); ++j) {
72 if (remove_iter != indices.end() && *remove_iter == j) {
73 ++remove_iter;
74 } else {
75 array->SwapElements(j, i++);
76 }
77 }
78 array->DeleteSubrange(i, array->size() - i);
79 }
80
81 // Removes the given element from array.
82 template <typename T>
Remove(protobuf::RepeatedPtrField<T> * array,const T * elem)83 void Remove(protobuf::RepeatedPtrField<T>* array, const T* elem) {
84 int i = Find(*array, [elem](const T* e) { return elem == e; });
85 RemoveAt(array, {i});
86 }
87
88 template <typename T, typename Pred>
RemoveIf(protobuf::RepeatedPtrField<T> * array,Pred && pred)89 void RemoveIf(protobuf::RepeatedPtrField<T>* array, Pred&& pred) {
90 std::vector<int> indices = FindAll(*array, pred);
91 RemoveAt(array, indices);
92 }
93
94 } // namespace
95
FindPlaneWithName(const XSpace & space,absl::string_view name)96 const XPlane* FindPlaneWithName(const XSpace& space, absl::string_view name) {
97 int i = Find(space.planes(),
98 [name](const XPlane* plane) { return plane->name() == name; });
99 return (i != -1) ? &space.planes(i) : nullptr;
100 }
101
FindPlanesWithNames(const XSpace & space,const std::vector<absl::string_view> & names)102 std::vector<const XPlane*> FindPlanesWithNames(
103 const XSpace& space, const std::vector<absl::string_view>& names) {
104 absl::flat_hash_set<absl::string_view> names_set(names.begin(), names.end());
105 std::vector<int> indices =
106 FindAll(space.planes(), [&names_set](const XPlane* plane) {
107 return names_set.contains(plane->name());
108 });
109 std::vector<const XPlane*> planes;
110 planes.reserve(indices.size());
111 for (int i : indices) {
112 planes.push_back(&space.planes(i));
113 }
114 return planes;
115 }
116
FindMutablePlaneWithName(XSpace * space,absl::string_view name)117 XPlane* FindMutablePlaneWithName(XSpace* space, absl::string_view name) {
118 int i = Find(space->planes(),
119 [name](const XPlane* plane) { return plane->name() == name; });
120 return (i != -1) ? space->mutable_planes(i) : nullptr;
121 }
122
FindOrAddMutablePlaneWithName(XSpace * space,absl::string_view name)123 XPlane* FindOrAddMutablePlaneWithName(XSpace* space, absl::string_view name) {
124 XPlane* plane = FindMutablePlaneWithName(space, name);
125 if (plane == nullptr) {
126 plane = space->add_planes();
127 plane->set_name(name.data(), name.size());
128 }
129 return plane;
130 }
131
FindPlanesWithPrefix(const XSpace & space,absl::string_view prefix)132 std::vector<const XPlane*> FindPlanesWithPrefix(const XSpace& space,
133 absl::string_view prefix) {
134 std::vector<const XPlane*> result;
135 for (const XPlane& plane : space.planes()) {
136 if (absl::StartsWith(plane.name(), prefix)) result.push_back(&plane);
137 }
138 return result;
139 }
140
FindMutablePlanesWithPrefix(XSpace * space,absl::string_view prefix)141 std::vector<XPlane*> FindMutablePlanesWithPrefix(XSpace* space,
142 absl::string_view prefix) {
143 std::vector<XPlane*> result;
144 for (XPlane& plane : *space->mutable_planes()) {
145 if (absl::StartsWith(plane.name(), prefix)) result.push_back(&plane);
146 }
147 return result;
148 }
149
FindLineWithId(const XPlane & plane,int64 id)150 const XLine* FindLineWithId(const XPlane& plane, int64 id) {
151 int i =
152 Find(plane.lines(), [id](const XLine* line) { return line->id() == id; });
153 return (i != -1) ? &plane.lines(i) : nullptr;
154 }
155
FindOrAddMutableStat(const XStatMetadata & stat_metadata,XEvent * event)156 XStat* FindOrAddMutableStat(const XStatMetadata& stat_metadata, XEvent* event) {
157 for (auto& stat : *event->mutable_stats()) {
158 if (stat.metadata_id() == stat_metadata.id()) {
159 return &stat;
160 }
161 }
162 XStat* stat = event->add_stats();
163 stat->set_metadata_id(stat_metadata.id());
164 return stat;
165 }
166
RemovePlane(XSpace * space,const XPlane * plane)167 void RemovePlane(XSpace* space, const XPlane* plane) {
168 DCHECK(plane != nullptr);
169 Remove(space->mutable_planes(), plane);
170 }
171
RemovePlanes(XSpace * space,const std::vector<const XPlane * > & planes)172 void RemovePlanes(XSpace* space, const std::vector<const XPlane*>& planes) {
173 absl::flat_hash_set<const XPlane*> planes_set(planes.begin(), planes.end());
174 RemoveIf(space->mutable_planes(), [&planes_set](const XPlane* plane) {
175 return planes_set.contains(plane);
176 });
177 }
178
RemoveLine(XPlane * plane,const XLine * line)179 void RemoveLine(XPlane* plane, const XLine* line) {
180 DCHECK(line != nullptr);
181 Remove(plane->mutable_lines(), line);
182 }
183
RemoveEvents(XLine * line,const absl::flat_hash_set<const XEvent * > & events)184 void RemoveEvents(XLine* line,
185 const absl::flat_hash_set<const XEvent*>& events) {
186 RemoveIf(line->mutable_events(),
187 [&](const XEvent* event) { return events.contains(event); });
188 }
189
RemoveEmptyPlanes(XSpace * space)190 void RemoveEmptyPlanes(XSpace* space) {
191 RemoveIf(space->mutable_planes(),
192 [&](const XPlane* plane) { return plane->lines().empty(); });
193 }
194
RemoveEmptyLines(XPlane * plane)195 void RemoveEmptyLines(XPlane* plane) {
196 RemoveIf(plane->mutable_lines(),
197 [&](const XLine* line) { return line->events().empty(); });
198 }
199
operator ()(const XEvent * a,const XEvent * b) const200 bool XEventsComparator::operator()(const XEvent* a, const XEvent* b) const {
201 return XEventTimespan(*a) < XEventTimespan(*b);
202 }
203
SortXPlane(XPlane * plane)204 void SortXPlane(XPlane* plane) {
205 for (XLine& line : *plane->mutable_lines()) {
206 auto& events = *line.mutable_events();
207 std::sort(events.pointer_begin(), events.pointer_end(),
208 XEventsComparator());
209 }
210 }
211
SortXSpace(XSpace * space)212 void SortXSpace(XSpace* space) {
213 for (XPlane& plane : *space->mutable_planes()) SortXPlane(&plane);
214 }
215
216 // Normalize the line's timestamp in this XPlane.
217 // NOTE: This can be called multiple times on the same plane. Only the first
218 // call will do the normalization, subsequent calls will do nothing.
219 // The assumption is that both line's timestamp_ns and start_time_ns are
220 // nano-seconds from epoch time, the different of these values is much
221 // smaller than these value.
NormalizeTimestamps(XPlane * plane,uint64 start_time_ns)222 void NormalizeTimestamps(XPlane* plane, uint64 start_time_ns) {
223 for (XLine& line : *plane->mutable_lines()) {
224 if (line.timestamp_ns() >= static_cast<int64>(start_time_ns)) {
225 line.set_timestamp_ns(line.timestamp_ns() - start_time_ns);
226 }
227 }
228 }
229
NormalizeTimestamps(XSpace * space,uint64 start_time_ns)230 void NormalizeTimestamps(XSpace* space, uint64 start_time_ns) {
231 for (XPlane& plane : *space->mutable_planes()) {
232 NormalizeTimestamps(&plane, start_time_ns);
233 }
234 }
235
MergePlanes(const XPlane & src_plane,XPlane * dst_plane)236 void MergePlanes(const XPlane& src_plane, XPlane* dst_plane) {
237 RemoveEmptyLines(dst_plane);
238 XPlaneVisitor src(&src_plane);
239 XPlaneBuilder dst(dst_plane);
240 src.ForEachStat([&](const tensorflow::profiler::XStatVisitor& stat) {
241 XStatMetadata* stat_metadata = dst.GetOrCreateStatMetadata(stat.Name());
242 // Use SetOrAddStat to avoid duplicating stats in dst_plane.
243 dst.SetOrAddStat(*stat_metadata, stat.RawStat(), src_plane);
244 });
245 src.ForEachLine([&](const tensorflow::profiler::XLineVisitor& line) {
246 XLineBuilder dst_line = dst.GetOrCreateLine(line.Id());
247 int64 time_offset_ps = 0LL;
248 if (dst_line.NumEvents() == 0) {
249 // Since we RemoveEmptyLines above, this could only mean that current
250 // line only exist in src plane.
251 dst_line.SetTimestampNs(line.TimestampNs());
252 dst_line.SetName(line.Name());
253 dst_line.SetDisplayNameIfEmpty(line.DisplayName());
254 } else {
255 if (line.TimestampNs() <= dst_line.TimestampNs()) {
256 dst_line.SetTimestampNsAndAdjustEventOffsets(line.TimestampNs());
257 } else {
258 time_offset_ps =
259 NanosToPicos(line.TimestampNs() - dst_line.TimestampNs());
260 }
261 dst_line.SetNameIfEmpty(line.Name());
262 // Don't override dst_line's display name because if both lines have name,
263 // but no display name, line's name will became display name of dst_line.
264 }
265
266 line.ForEachEvent([&](const tensorflow::profiler::XEventVisitor& event) {
267 const XEventMetadata* src_event_metadata = event.metadata();
268 XEventMetadata* dst_event_metadata =
269 dst.GetOrCreateEventMetadata(event.Name());
270 if (dst_event_metadata->display_name().empty() &&
271 !src_event_metadata->display_name().empty()) {
272 dst_event_metadata->set_display_name(
273 src_event_metadata->display_name());
274 }
275 if (dst_event_metadata->metadata().empty() &&
276 !src_event_metadata->metadata().empty()) {
277 dst_event_metadata->set_metadata(src_event_metadata->metadata());
278 }
279 XEventBuilder dst_event = dst_line.AddEvent(*dst_event_metadata);
280 dst_event.SetOffsetPs(event.OffsetPs() + time_offset_ps);
281 dst_event.SetDurationPs(event.DurationPs());
282 if (event.NumOccurrences()) {
283 dst_event.SetNumOccurrences(event.NumOccurrences());
284 }
285 event.ForEachStat([&](const tensorflow::profiler::XStatVisitor& stat) {
286 // Here we can call AddStat instead of SetOrAddStat because dst_event
287 // was just added.
288 dst_event.AddStat(*dst.GetOrCreateStatMetadata(stat.Name()),
289 stat.RawStat(), src_plane);
290 });
291 });
292 });
293 }
294
MergePlanes(const std::vector<const XPlane * > & src_planes,XPlane * dst_plane)295 void MergePlanes(const std::vector<const XPlane*>& src_planes,
296 XPlane* dst_plane) {
297 for (const XPlane* src_plane : src_planes) {
298 MergePlanes(*src_plane, dst_plane);
299 }
300 }
301
GetStartTimestampNs(const XPlane & plane)302 uint64 GetStartTimestampNs(const XPlane& plane) {
303 int64 plane_timestamp = 0;
304 for (const auto& line : plane.lines()) {
305 plane_timestamp = std::min<int64>(plane_timestamp, line.timestamp_ns());
306 }
307 return plane_timestamp;
308 }
309
IsEmpty(const XSpace & space)310 bool IsEmpty(const XSpace& space) {
311 for (const auto& plane : space.planes()) {
312 for (const auto& line : plane.lines()) {
313 if (!line.events().empty()) {
314 return false;
315 }
316 }
317 }
318 return true;
319 }
320
321 } // namespace profiler
322 } // namespace tensorflow
323