blob: 6096d7188f350669066592cd8b8151261163e720 [file] [log] [blame]
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/core/profiler/utils/xplane_utils.h"
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "tensorflow/core/platform/env_time.h"
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/core/platform/types.h"
#include "tensorflow/core/profiler/protobuf/xplane.pb.h"
#include "tensorflow/core/profiler/utils/timespan.h"
#include "tensorflow/core/profiler/utils/xplane_builder.h"
#include "tensorflow/core/profiler/utils/xplane_visitor.h"
namespace tensorflow {
namespace profiler {
namespace {
// Creates a Timespan from an XEvent.
// WARNING: This should only be used when comparing events from the same XLine.
Timespan XEventTimespan(const XEvent& event) {
return Timespan(event.offset_ps(), event.duration_ps());
}
// Creates a Timespan from a non-empty XLine.
Timespan XLineTimespan(const XLine& line) {
uint64 begin_ps = kuint64max, end_ps = 0;
for (const XEvent& event : line.events()) {
// Don't use XEventTimespan. We need the absolute event start time as lines
// might have different timestamps.
Timespan span(line.timestamp_ns() * 1000 + event.offset_ps(),
event.duration_ps());
begin_ps = std::min(span.begin_ps(), begin_ps);
end_ps = std::max(span.end_ps(), end_ps);
}
return Timespan::FromEndPoints(begin_ps, end_ps);
}
// Functor that compares XEvents of the same XLine for sorting by timespan.
struct XEventsComparator {
bool operator()(const XEvent* a, const XEvent* b) const {
return XEventTimespan(*a) < XEventTimespan(*b);
}
};
// Functor that compares XLines of the same XPlane for sorting by timespan.
class XLinesComparator {
public:
bool operator()(const XLine* a, const XLine* b) const {
return CachedXLineTimespan(a) < CachedXLineTimespan(b);
}
private:
Timespan CachedXLineTimespan(const XLine* line) const {
DCHECK_GT(line->events_size(), 0);
Timespan& line_timespan = line_timespan_[line];
if (line_timespan.Instant()) {
line_timespan = XLineTimespan(*line);
}
return line_timespan;
}
mutable absl::flat_hash_map<const XLine*, Timespan> line_timespan_;
};
} // namespace
const XPlane* FindPlaneWithName(const XSpace& space, absl::string_view name) {
for (const XPlane& plane : space.planes()) {
if (plane.name() == name) return &plane;
}
return nullptr;
}
std::vector<const XPlane*> FindPlanesWithPrefix(const XSpace& space,
absl::string_view prefix) {
std::vector<const XPlane*> result;
for (const XPlane& plane : space.planes()) {
if (absl::StartsWith(plane.name(), prefix)) result.push_back(&plane);
}
return result;
}
XPlane* GetOrCreatePlane(XSpace* space, absl::string_view name) {
for (XPlane& plane : *space->mutable_planes()) {
if (plane.name() == name) return &plane;
}
XPlane* plane = space->add_planes();
plane->set_name(std::string(name));
return plane;
}
bool IsNested(const XEvent& event, const XEvent& parent) {
return XEventTimespan(parent).Includes(XEventTimespan(event));
}
void AddOrUpdateIntStat(int64 metadata_id, int64 value, XEvent* event) {
for (auto& stat : *event->mutable_stats()) {
if (stat.metadata_id() == metadata_id) {
stat.set_int64_value(value);
return;
}
}
XStat* stat = event->add_stats();
stat->set_metadata_id(metadata_id);
stat->set_int64_value(value);
}
void AddOrUpdateStrStat(int64 metadata_id, absl::string_view value,
XEvent* event) {
for (auto& stat : *event->mutable_stats()) {
if (stat.metadata_id() == metadata_id) {
stat.set_str_value(std::string(value));
return;
}
}
XStat* stat = event->add_stats();
stat->set_metadata_id(metadata_id);
stat->set_str_value(std::string(value));
}
void RemovePlaneWithName(XSpace* space, absl::string_view name) {
auto* planes = space->mutable_planes();
planes->erase(
std::remove_if(planes->begin(), planes->end(),
[&](const XPlane& plane) { return plane.name() == name; }),
planes->end());
}
void RemoveEmptyPlanes(XSpace* space) {
auto* planes = space->mutable_planes();
planes->erase(std::remove_if(planes->begin(), planes->end(),
[&](const XPlane& plane) {
return plane.lines_size() == 0;
}),
planes->end());
}
void RemoveEmptyLines(XPlane* plane) {
auto* lines = plane->mutable_lines();
lines->erase(std::remove_if(
lines->begin(), lines->end(),
[&](const XLine& line) { return line.events_size() == 0; }),
lines->end());
}
XPlane* FindMutablePlaneWithName(XSpace* space, absl::string_view name) {
for (XPlane& plane : *space->mutable_planes()) {
if (plane.name() == name) return &plane;
}
return nullptr;
}
XPlane* FindOrAddMutablePlaneWithName(XSpace* space, absl::string_view name) {
XPlane* plane = FindMutablePlaneWithName(space, name);
if (plane == nullptr) {
plane = space->add_planes();
plane->set_name(std::string(name));
}
return plane;
}
void SortXPlane(XPlane* plane) {
for (XLine& line : *plane->mutable_lines()) {
auto& events = *line.mutable_events();
std::sort(events.pointer_begin(), events.pointer_end(),
XEventsComparator());
}
SortXLinesBy(plane, XLinesComparator());
}
void SortXSpace(XSpace* space) {
for (XPlane& plane : *space->mutable_planes()) SortXPlane(&plane);
}
// Normalize the line's timestamp in this XPlane.
// NOTE: This can be called multiple times on the same plane. Only the first
// call will do the normalization, subsequent calls will do nothing.
// The assumption is that both line's timestamp_ns and start_time_ns are
// nano-seconds from epoch time, the different of these values is much
// smaller than these value.
void NormalizeTimestamps(XPlane* plane, uint64 start_time_ns) {
for (XLine& line : *plane->mutable_lines()) {
if (line.timestamp_ns() >= start_time_ns) {
line.set_timestamp_ns(line.timestamp_ns() - start_time_ns);
}
}
}
void NormalizeTimestamps(XSpace* space, uint64 start_time_ns) {
for (XPlane& plane : *space->mutable_planes()) {
NormalizeTimestamps(&plane, start_time_ns);
}
}
void MergePlanes(const XPlane& src_plane, XPlane* dst_plane) {
RemoveEmptyLines(dst_plane);
XPlaneVisitor src(&src_plane);
XPlaneBuilder dst(dst_plane);
src.ForEachStat([&](const tensorflow::profiler::XStatVisitor& stat) {
XStatMetadata* stat_metadata = dst.GetOrCreateStatMetadata(stat.Name());
XStat* new_stat = dst.FindOrAddMutableStat(stat_metadata->id());
// Add or override the existing stat value except the metadata id.
*new_stat = stat.RawStat();
new_stat->set_metadata_id(stat_metadata->id());
});
src.ForEachLine([&](const tensorflow::profiler::XLineVisitor& line) {
XLineBuilder dst_line = dst.GetOrCreateLine(line.Id());
int64 time_offset_ps = 0LL;
if (dst_line.NumEvents() == 0) {
// Since we RemoveEmptyLines above, this could only mean that current
// line only exist in src plane.
dst_line.SetTimestampNs(line.TimestampNs());
dst_line.SetName(line.Name());
dst_line.SetDisplayNameIfEmpty(line.DisplayName());
} else {
if (line.TimestampNs() <= dst_line.TimestampNs()) {
dst_line.SetTimestampNsAndAdjustEventOffsets(line.TimestampNs());
} else {
time_offset_ps = (line.TimestampNs() - dst_line.TimestampNs()) *
EnvTime::kNanosToPicos;
}
dst_line.SetNameIfEmpty(line.Name());
// Don't override dst_line's display name because if both lines have name,
// but no display name, line's name will became display name of dst_line.
}
line.ForEachEvent([&](const tensorflow::profiler::XEventVisitor& event) {
const XEventMetadata* src_event_metadata = event.metadata();
XEventMetadata* dst_event_metadata =
dst.GetOrCreateEventMetadata(event.Name());
if (dst_event_metadata->display_name().empty() &&
!src_event_metadata->display_name().empty()) {
dst_event_metadata->set_display_name(
src_event_metadata->display_name());
}
if (dst_event_metadata->metadata().empty() &&
!src_event_metadata->metadata().empty()) {
dst_event_metadata->set_metadata(src_event_metadata->metadata());
}
XEventBuilder dst_event = dst_line.AddEvent(*dst_event_metadata);
dst_event.SetOffsetPs(event.OffsetPs() + time_offset_ps);
dst_event.SetDurationPs(event.DurationPs());
if (event.NumOccurrences()) {
dst_event.SetNumOccurrences(event.NumOccurrences());
}
event.ForEachStat([&](const tensorflow::profiler::XStatVisitor& stat) {
dst_event.AddStat(*dst.GetOrCreateStatMetadata(stat.Name()),
stat.RawStat(), src_plane);
});
});
});
}
uint64 GetStartTimestampNs(const XPlane& plane) {
int64 plane_timestamp = 0;
for (const auto& line : plane.lines()) {
plane_timestamp = std::min<int64>(plane_timestamp, line.timestamp_ns());
}
return plane_timestamp;
}
} // namespace profiler
} // namespace tensorflow