blob: 322bb4b72ee8e0db675014d3c51d31093d421cc2 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 "kudu/tablet/svg_dump.h"
#include <algorithm>
#include <ctime>
#include <string>
#include <unordered_set>
#include <vector>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include "kudu/gutil/macros.h"
#include "kudu/gutil/map-util.h"
#include "kudu/gutil/stringprintf.h"
#include "kudu/gutil/strings/substitute.h"
#include "kudu/gutil/strings/util.h"
#include "kudu/tablet/rowset_info.h"
#include "kudu/util/env.h"
#include "kudu/util/flag_tags.h"
#include "kudu/util/slice.h"
#include "kudu/util/status.h"
// Flag to dump SVGs of every compaction decision.
//
// After dumping, these may be converted to an animation using a series of
// commands like:
// $ for x in compaction-*svg ; do convert $x $x.png ; done
// $ mencoder mf://compaction*png -mf fps=1 -ovc lavc -o compactions.avi
DEFINE_string(compaction_policy_dump_svgs_pattern, "",
"File path into which to dump SVG visualization of "
"selected compactions. This is mostly useful in "
"the context of unit tests and benchmarks. "
"The special string 'TIME' will be substituted "
"with the compaction selection timestamp.");
TAG_FLAG(compaction_policy_dump_svgs_pattern, hidden);
using std::endl;
using std::ostream;
using std::string;
using std::unordered_set;
using std::vector;
using strings::Substitute;
namespace kudu {
namespace tablet {
namespace {
// Organize the input rowsets into rows for presentation. This simply
// distributes 'rowsets' into separate vectors in 'rows' such that
// within any given row, none of the rowsets overlap in keyspace.
void OrganizeSVGRows(const vector<RowSetInfo>& candidates,
vector<vector<const RowSetInfo*>>* rows) {
DCHECK(rows);
rows->clear();
for (const auto& candidate : candidates) {
// Slot into the first row which fits it.
bool found_slot = false;
for (auto& row : *rows) {
// If this candidate doesn't intersect any other rowsets already in this
// row, we can put it in this row.
auto fits_in_row = std::none_of(row.begin(),
row.end(),
[&candidate](const RowSetInfo* already_in_row) {
return candidate.Intersects(*already_in_row);
});
if (fits_in_row) {
row.push_back(&candidate);
found_slot = true;
break;
}
}
// If we couldn't find a spot in any existing row, add a new row
// to the bottom of the SVG.
if (!found_slot) {
rows->push_back({ &candidate });
}
}
}
void DumpSVG(const vector<RowSetInfo>& candidates,
const unordered_set<const RowSet*>& picked,
ostream* outptr) {
CHECK(outptr);
CHECK(outptr->good());
ostream& out = *outptr;
vector<vector<const RowSetInfo*>> svg_rows;
OrganizeSVGRows(candidates, &svg_rows);
const char *kPickedColor = "#f66"; // Light red.
const char *kDefaultColor = "#666"; // Dark gray.
constexpr double kTotalWidth = 1200.0;
constexpr int kRowHeight = 15;
constexpr double kHeaderHeight = 60.0;
const double kTotalHeight = kRowHeight * svg_rows.size() + kHeaderHeight;
out << Substitute(
R"(<svg version="1.1" width="$0" height="$1" viewBox="0 0 $0 $1" )"
R"(xmlns="http://www.w3.org/2000/svg">)",
kTotalWidth, kTotalHeight)
<< endl;
// Background.
out << Substitute(R"(<rect x="0" y="0" width="$0" height="$1" fill="#fff"/>)",
kTotalWidth, kTotalHeight)
<< endl;
for (auto row_index = 0; row_index < svg_rows.size(); row_index++) {
const vector<const RowSetInfo *> &row = svg_rows[row_index];
const auto y = kRowHeight * row_index + kHeaderHeight;
for (const RowSetInfo *cand : row) {
const char *color = ContainsKey(picked, cand->rowset()) ? kPickedColor :
kDefaultColor;
const auto x = cand->cdf_min_key() * kTotalWidth;
const auto width = cand->width() * kTotalWidth;
out << Substitute(
R"(<rect x="$0" y="$1" width="$2" height="$3" stroke="#000" fill="$4"/>)",
x, y, width, kRowHeight, color)
<< endl;
}
}
out << "</svg>" << endl;
}
void PrintXMLHeader(ostream* out) {
CHECK(out);
CHECK(out->good());
*out << R"(<?xml version="1.0" standalone="no"?>)"
<< endl
<< R"(<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" )"
<< R"("http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">)"
<< endl;
}
} // anonymous namespace
void DumpCompactionSVG(const vector<RowSetInfo>& candidates,
const unordered_set<const RowSet*>& picked,
ostream* out,
bool print_xml_header) {
CHECK(out);
VLOG(1) << "Dumping SVG of DiskRowSetLayout with"
<< (print_xml_header ? "" : "out") << " XML header";
if (print_xml_header) {
PrintXMLHeader(out);
}
DumpSVG(candidates, picked, out);
}
void DumpCompactionSVGToFile(const vector<RowSetInfo>& candidates,
const unordered_set<const RowSet*>& picked) {
const string& pattern = FLAGS_compaction_policy_dump_svgs_pattern;
if (pattern.empty()) {
return;
}
const string path = StringReplace(pattern,
"TIME",
StringPrintf("%ld", time(nullptr)),
/*replace_all=*/true);
std::ostringstream buf;
DumpCompactionSVG(candidates, picked, &buf, /*print_xml_header=*/true);
WARN_NOT_OK(WriteStringToFile(Env::Default(), buf.str(), path),
"unable to dump rowset compaction SVG to file");
}
} // namespace tablet
} // namespace kudu