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