foundationdb/flow/CodeProbe.cpp

294 lines
8.5 KiB
C++

/*
* CodeProbe.cpp
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
*
* 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 "flow/CodeProbe.h"
#include "flow/CodeProbeUtils.h"
#include "flow/Arena.h"
#include "flow/network.h"
#include <map>
#include <fmt/format.h>
#include <boost/core/demangle.hpp>
#include <boost/unordered_map.hpp>
#include <typeinfo>
namespace probe {
namespace {
std::vector<ExecutionContext> fromStrings(std::vector<std::string> const& ctxs) {
std::vector<ExecutionContext> res;
for (auto const& ctx : ctxs) {
std::string c;
c.reserve(ctx.size());
std::transform(ctx.begin(), ctx.end(), std::back_inserter(c), [](char c) { return std::tolower(c); });
if (c == "all") {
res.push_back(ExecutionContext::Net2);
res.push_back(ExecutionContext::Simulation);
} else if (c == "simulation") {
res.push_back(ExecutionContext::Simulation);
} else if (c == "net2") {
res.push_back(ExecutionContext::Net2);
} else {
throw invalid_option_value();
}
}
std::sort(res.begin(), res.end());
res.erase(std::unique(res.begin(), res.end()), res.end());
return res;
}
std::string_view normalizePath(const char* path) {
std::string_view srcBase(FDB_SOURCE_DIR);
std::string_view binBase(FDB_SOURCE_DIR);
std::string_view filename(path);
if (srcBase.size() < filename.size() && filename.substr(0, srcBase.size()) == srcBase) {
filename.remove_prefix(srcBase.size());
} else if (binBase.size() < filename.size() && filename.substr(0, binBase.size()) == binBase) {
filename.remove_prefix(binBase.size());
}
if (filename[0] == '/') {
filename.remove_prefix(1);
}
return filename;
}
struct CodeProbes {
struct Location {
std::string_view file;
unsigned line;
Location(std::string_view file, unsigned line) : file(file), line(line) {}
bool operator==(Location const& rhs) const { return line == rhs.line && file == rhs.file; }
bool operator!=(Location const& rhs) const { return line != rhs.line && file != rhs.file; }
bool operator<(Location const& rhs) const {
if (file < rhs.file) {
return true;
} else if (file == rhs.file) {
return line < rhs.line;
} else {
return false;
}
}
bool operator<=(Location const& rhs) const {
if (file < rhs.file) {
return true;
} else if (file == rhs.file) {
return line <= rhs.line;
} else {
return false;
}
}
bool operator>(Location const& rhs) const { return rhs < *this; }
bool operator>=(Location const& rhs) const { return rhs <= *this; }
};
std::multimap<Location, ICodeProbe const*> codeProbes;
void traceMissedProbes(Optional<ExecutionContext> context) const;
void add(ICodeProbe const* probe) {
Location loc(probe->filename(), probe->line());
codeProbes.emplace(loc, probe);
}
static CodeProbes& instance() {
static CodeProbes probes;
return probes;
}
void verify() const {
std::map<std::pair<std::string_view, std::string_view>, ICodeProbe const*> comments;
for (auto probe : codeProbes) {
auto file = probe.first.file;
auto comment = probe.second->comment();
auto commentEntry = std::make_pair(file, std::string_view(comment));
ASSERT(file == probe.second->filename());
auto iter = comments.find(commentEntry);
if (iter != comments.end() && probe.second->line() != iter->second->line()) {
fmt::print("ERROR ({}:{}): {} isn't unique in file {}. Previously seen here: {}:{}\n",
probe.first.file,
probe.first.line,
iter->first.second,
probe.second->filename(),
iter->second->filename(),
iter->second->line());
// ICodeProbe const& fst = *iter->second;
// ICodeProbe const& snd = *probe.second;
// fmt::print("\t1st Type: {}\n", boost::core::demangle(typeid(fst).name()));
// fmt::print("\t2nd Type: {}\n", boost::core::demangle(typeid(snd).name()));
// fmt::print("\n");
// fmt::print("\t1st Comment: {}\n", fst.comment());
// fmt::print("\t2nd Comment: {}\n", snd.comment());
// fmt::print("\n");
// fmt::print("\t1st CompUnit: {}\n", fst.compilationUnit());
// fmt::print("\t2nd CompUnit: {}\n", snd.compilationUnit());
// fmt::print("\n");
} else {
comments.emplace(commentEntry, probe.second);
}
}
}
void printXML() const {
verify();
fmt::print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
fmt::print("<CoverageTool>\n");
if (codeProbes.empty()) {
fmt::print("\t<CoverageCases/>\n");
fmt::print("\t<Inputs/>\n");
} else {
std::vector<std::string_view> files;
fmt::print("\t<CoverageCases>\n");
for (auto probe : codeProbes) {
files.push_back(probe.first.file);
fmt::print("\t\t<Case File=\"{}\" Line=\"{}\" Comment=\"{}\" Condition=\"{}\"/>\n",
probe.first.file,
probe.first.line,
probe.second->comment(),
probe.second->condition());
}
fmt::print("\t</CoverageCases>\n");
fmt::print("\t<Inputs>\n");
for (auto const& f : files) {
fmt::print("\t\t<Input>{}</Input>\n", f);
}
fmt::print("\t</Inputs>\n");
}
fmt::print("</CoverageTool>\n");
}
void printJSON(std::vector<std::string> const& context = std::vector<std::string>()) const {
verify();
do {
struct foo {};
foo f;
fmt::print("{}\n", boost::core::demangle(typeid(f).name()));
} while (false);
do {
struct foo {};
foo f;
fmt::print("{}\n", boost::core::demangle(typeid(f).name()));
} while (false);
auto contexts = fromStrings(context);
const ICodeProbe* prev = nullptr;
for (auto probe : codeProbes) {
auto p = probe.second;
if (!contexts.empty()) {
bool print = false;
for (auto c : contexts) {
print = print || p->expectInContext(c);
}
if (!print) {
continue;
}
}
if (prev == nullptr || *prev != *probe.second) {
fmt::print(
"{{ \"File\": \"{}\", \"Line\": {}, \"Comment\": \"{}\", \"Condition\": \"{}\", \"Function\": "
"\"{}\" }}\n",
probe.first.file,
p->line(),
p->comment(),
p->condition(),
p->function());
}
prev = probe.second;
}
}
};
size_t hash_value(CodeProbes::Location const& location) {
size_t seed = 0;
boost::hash_combine(seed, location.file);
boost::hash_combine(seed, location.line);
return seed;
}
void CodeProbes::traceMissedProbes(Optional<ExecutionContext> context) const {
boost::unordered_map<Location, bool> locations;
for (auto probe : codeProbes) {
decltype(locations.begin()) iter;
std::tie(iter, std::ignore) = locations.emplace(probe.first, false);
iter->second = iter->second || probe.second->wasHit();
}
for (const auto& [loc, probe] : codeProbes) {
auto iter = locations.find(loc);
ASSERT(iter != locations.end());
if (!iter->second && probe->shouldTrace()) {
iter->second = true;
probe->trace(false);
}
}
}
} // namespace
std::string functionNameFromInnerType(const char* name) {
auto res = boost::core::demangle(name);
auto pos = res.find_last_of(':');
ASSERT(pos != res.npos);
return res.substr(0, pos - 1);
}
void registerProbe(const ICodeProbe& probe) {
CodeProbes::instance().add(&probe);
}
void traceMissedProbes(Optional<ExecutionContext> context) {
CodeProbes::instance().traceMissedProbes(context);
}
ICodeProbe::~ICodeProbe() {}
bool ICodeProbe::operator==(const ICodeProbe& other) const {
return filename() == other.filename() && line() == other.line();
}
bool ICodeProbe::operator!=(const ICodeProbe& other) const {
return !(*this == other);
}
std::string_view ICodeProbe::filename() const {
return normalizePath(filePath());
}
void ICodeProbe::printProbesXML() {
CodeProbes::instance().printXML();
}
void ICodeProbe::printProbesJSON(std::vector<std::string> const& ctxs) {
CodeProbes::instance().printJSON(ctxs);
}
// annotations
namespace assert {
bool NoSim::operator()(ICodeProbe const* self) const {
return !g_network->isSimulated();
}
bool SimOnly::operator()(ICodeProbe const* self) const {
return g_network->isSimulated();
}
} // namespace assert
} // namespace probe