cpp11-migrate: Transforms collect timing data.

Using updated form of newFrontendActionFactory(), Transforms now automatically
measure, if requested, how long it takes to apply a MatchFinder to a source
file. Other per-transform overhead, e.g. applying replacements, is not
currently measured. This behaviour is disabled for now and soon will be
connected to a new command line arg.

llvm-svn: 182942
This commit is contained in:
Edwin Vane 2013-05-30 17:48:11 +00:00
parent 8fe6d11b84
commit b76a13eb4e
14 changed files with 172 additions and 20 deletions

View File

@ -45,7 +45,8 @@ int AddOverrideTransform::apply(const FileContentsByPath &InputStates,
Finder.addMatcher(makeCandidateForOverrideAttrMatcher(), &Fixer);
if (int result = AddOverrideTool.run(newFrontendActionFactory(&Finder))) {
if (int result = AddOverrideTool.run(
newFrontendActionFactory(&Finder, /*Callbacks=*/ this))) {
llvm::errs() << "Error encountered during translation.\n";
return result;
}

View File

@ -24,7 +24,8 @@
/// member functions overriding base class virtual functions.
class AddOverrideTransform : public Transform {
public:
AddOverrideTransform() : Transform("AddOverride") {}
AddOverrideTransform(bool EnableTiming)
: Transform("AddOverride", EnableTiming) {}
/// \see Transform::run().
virtual int apply(const FileContentsByPath &InputStates,

View File

@ -35,3 +35,19 @@ void collectResults(clang::Rewriter &Rewrite,
Results[Entry->getName()] = ResultBuf;
}
}
bool Transform::handleBeginSource(CompilerInstance &CI, StringRef Filename) {
if (!EnableTiming)
return true;
Timings.push_back(std::make_pair(Filename.str(), llvm::TimeRecord()));
Timings.back().second -= llvm::TimeRecord::getCurrentTime(true);
return true;
}
void Transform::handleEndSource() {
if (!EnableTiming)
return;
Timings.back().second += llvm::TimeRecord::getCurrentTime(false);
}

View File

@ -17,6 +17,8 @@
#include <string>
#include <vector>
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/Timer.h"
// For RewriterContainer
#include "clang/Rewrite/Core/Rewriter.h"
@ -104,9 +106,23 @@ private:
};
/// \brief Abstract base class for all C++11 migration transforms.
class Transform {
///
/// Per-source performance timing is handled by the callbacks
/// handleBeginSource() and handleEndSource() if timing is enabled. See
/// clang::tooling::newFrontendActionFactory() for how to register
/// a Transform object for callbacks.
class Transform : public clang::tooling::SourceFileCallbacks {
public:
Transform(llvm::StringRef Name) : Name(Name) {
/// \brief Constructor
/// \param Name Name of the transform for human-readable purposes (e.g. -help
/// text)
/// \param EnableTiming Enable the timing of the duration between calls to
/// handleBeginSource() and handleEndSource(). When a Transform object is
/// registered for FrontendAction source file callbacks, this behaviour can
/// be used to time the application of a MatchFinder by subclasses. Durations
/// are automatically stored in a TimingVec.
Transform(llvm::StringRef Name, bool EnableTiming)
: Name(Name), EnableTiming(EnableTiming) {
Reset();
}
@ -156,7 +172,31 @@ public:
DeferredChanges = 0;
}
/// \brief Callback for notification of the start of processing of a source
/// file by a FrontendAction. Starts a performance timer if timing was
/// enabled.
virtual bool handleBeginSource(clang::CompilerInstance &CI,
llvm::StringRef Filename) LLVM_OVERRIDE;
/// \brief Callback for notification of the end of processing of a source
/// file by a FrontendAction. Stops a performance timer if timing was enabled
/// and records the elapsed time. For a given source, handleBeginSource() and
/// handleEndSource() are expected to be called in pairs.
virtual void handleEndSource() LLVM_OVERRIDE;
/// \brief Performance timing data is stored as a vector of pairs. Pairs are
/// formed of:
/// \li Name of source file.
/// \li Elapsed time.
typedef std::vector<std::pair<std::string, llvm::TimeRecord> > TimingVec;
/// \brief Return an iterator to the start of collected timing data.
TimingVec::const_iterator timing_begin() const { return Timings.begin(); }
/// \brief Return an iterator to the start of collected timing data.
TimingVec::const_iterator timing_end() const { return Timings.end(); }
protected:
void setAcceptedChanges(unsigned Changes) {
AcceptedChanges = Changes;
}
@ -169,6 +209,8 @@ protected:
private:
const std::string Name;
bool EnableTiming;
TimingVec Timings;
unsigned AcceptedChanges;
unsigned RejectedChanges;
unsigned DeferredChanges;

View File

@ -35,11 +35,11 @@ void Transforms::registerTransform(llvm::StringRef OptName,
new cl::opt<bool>(OptName.data(), cl::desc(Description.data())), Creator));
}
void Transforms::createSelectedTransforms() {
void Transforms::createSelectedTransforms(bool EnableTiming) {
for (OptionVec::iterator I = Options.begin(),
E = Options.end(); I != E; ++I) {
if (*I->first) {
ChosenTransforms.push_back(I->second());
ChosenTransforms.push_back(I->second(EnableTiming));
}
}
}

View File

@ -28,10 +28,10 @@ class Option;
} // namespace llvm
class Transform;
typedef Transform *(*TransformCreator)();
typedef Transform *(*TransformCreator)(bool);
template <typename T>
Transform *ConstructTransform() {
return new T();
Transform *ConstructTransform(bool EnableTiming) {
return new T(EnableTiming);
}
/// \brief Class encapsulating the creation of command line bool options
@ -55,7 +55,7 @@ public:
/// \brief Instantiate all transforms that were selected on the command line.
///
/// Call *after* parsing options.
void createSelectedTransforms();
void createSelectedTransforms(bool EnableTiming);
/// \brief Return an iterator to the start of a container of instantiated
/// transforms.

View File

@ -64,7 +64,8 @@ int LoopConvertTransform::apply(const FileContentsByPath &InputStates,
MaxRisk, LFK_PseudoArray);
Finder.addMatcher(makePseudoArrayLoopMatcher(), &PseudoarrrayLoopFixer);
if (int result = LoopTool.run(newFrontendActionFactory(&Finder))) {
if (int result = LoopTool.run(
newFrontendActionFactory(&Finder, /*Callbacks=*/ this))) {
llvm::errs() << "Error encountered during translation.\n";
return result;
}

View File

@ -23,7 +23,8 @@
/// for-loops where possible.
class LoopConvertTransform : public Transform {
public:
LoopConvertTransform() : Transform("LoopConvert") {}
LoopConvertTransform(bool EnableTiming)
: Transform("LoopConvert", EnableTiming) {}
/// \see Transform::run().
virtual int apply(const FileContentsByPath &InputStates,

View File

@ -43,7 +43,8 @@ int UseAutoTransform::apply(const FileContentsByPath &InputStates,
Finder.addMatcher(makeIteratorDeclMatcher(), &ReplaceIterators);
Finder.addMatcher(makeDeclWithNewMatcher(), &ReplaceNew);
if (int Result = UseAutoTool.run(newFrontendActionFactory(&Finder))) {
if (int Result = UseAutoTool.run(
newFrontendActionFactory(&Finder, /*Callbacks=*/ this))) {
llvm::errs() << "Error encountered during translation.\n";
return Result;
}

View File

@ -29,7 +29,7 @@
/// p2 are not handled by this transform.
class UseAutoTransform : public Transform {
public:
UseAutoTransform() : Transform("UseAuto") {}
UseAutoTransform(bool EnableTiming) : Transform("UseAuto", EnableTiming) {}
/// \see Transform::run().
virtual int apply(const FileContentsByPath &InputStates,

View File

@ -47,7 +47,8 @@ int UseNullptrTransform::apply(const FileContentsByPath &InputStates,
Finder.addMatcher(makeCastSequenceMatcher(), &Fixer);
if (int result = UseNullptrTool.run(newFrontendActionFactory(&Finder))) {
if (int result = UseNullptrTool.run(
newFrontendActionFactory(&Finder, /*Callbacks=*/ this))) {
llvm::errs() << "Error encountered during translation.\n";
return result;
}

View File

@ -23,7 +23,8 @@
/// C++11's nullptr keyword where possible.
class UseNullptrTransform : public Transform {
public:
UseNullptrTransform() : Transform("UseNullptr") {}
UseNullptrTransform(bool EnableTiming)
: Transform("UseNullptr", EnableTiming) {}
/// \see Transform::run().
virtual int apply(const FileContentsByPath &InputStates,

View File

@ -99,7 +99,7 @@ int main(int argc, const char **argv) {
// This causes options to be parsed.
CommonOptionsParser OptionsParser(argc, argv);
TransformManager.createSelectedTransforms();
TransformManager.createSelectedTransforms(/*EnableTiming=*/false);
if (TransformManager.begin() == TransformManager.end()) {
llvm::errs() << "No selected transforms\n";

View File

@ -1,13 +1,20 @@
#include "gtest/gtest.h"
#include "Core/Transform.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/DeclGroup.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Path.h"
using namespace clang;
class DummyTransform : public Transform {
public:
DummyTransform(llvm::StringRef Name) : Transform(Name) {}
DummyTransform(llvm::StringRef Name, bool EnableTiming)
: Transform(Name, EnableTiming) {}
virtual int apply(const FileContentsByPath &,
RiskLevel ,
const clang::tooling::CompilationDatabase &,
const tooling::CompilationDatabase &,
const std::vector<std::string> &,
FileContentsByPath &) { return 0; }
@ -23,7 +30,7 @@ public:
};
TEST(Transform, Interface) {
DummyTransform T("my_transform");
DummyTransform T("my_transform", /*EnableTiming=*/false);
ASSERT_EQ("my_transform", T.getName());
ASSERT_EQ(0u, T.getAcceptedChanges());
ASSERT_EQ(0u, T.getRejectedChanges());
@ -48,3 +55,83 @@ TEST(Transform, Interface) {
T.setRejectedChanges(1);
ASSERT_TRUE(T.getChangesNotMade());
}
class FindTopLevelDeclConsumer : public ASTConsumer {
public:
FindTopLevelDeclConsumer(bool *Called) : Called(Called) {}
virtual bool HandleTopLevelDecl(DeclGroupRef DeclGroup) {
llvm::sys::TimeValue UserStart;
llvm::sys::TimeValue SystemStart;
llvm::sys::TimeValue UserNow;
llvm::sys::TimeValue SystemNow;
llvm::sys::TimeValue Wall;
// Busy-wait until the user/system time combined is more than 1ms
llvm::sys::TimeValue OneMS(0, 1000000);
llvm::sys::Process::GetTimeUsage(Wall, UserStart, SystemStart);
do {
llvm::sys::Process::GetTimeUsage(Wall, UserNow, SystemNow);
} while (UserNow - UserStart + SystemNow - SystemStart < OneMS);
*Called = true;
return true;
}
bool *Called;
};
struct ConsumerFactory {
ASTConsumer *newASTConsumer() {
return new FindTopLevelDeclConsumer(&Called);
}
bool Called;
};
TEST(Transform, Timings) {
DummyTransform T("timing_transform", /*EnableTiming=*/true);
// All the path stuff is to make the test work independently of OS.
// The directory used is not important since the path gets mapped to a virtual
// file anyway. What is important is that we have an absolute path with which
// to use with mapVirtualFile().
llvm::sys::Path FileA = llvm::sys::Path::GetCurrentDirectory();
std::string CurrentDir = FileA.str();
FileA.appendComponent("a.cc");
std::string FileAName = FileA.str();
llvm::sys::Path FileB = llvm::sys::Path::GetCurrentDirectory();
FileB.appendComponent("b.cc");
std::string FileBName = FileB.str();
tooling::FixedCompilationDatabase Compilations(CurrentDir, std::vector<std::string>());
std::vector<std::string> Sources;
Sources.push_back(FileAName);
Sources.push_back(FileBName);
tooling::ClangTool Tool(Compilations, Sources);
Tool.mapVirtualFile(FileAName, "void a() {}");
Tool.mapVirtualFile(FileBName, "void b() {}");
ConsumerFactory Factory;
Tool.run(newFrontendActionFactory(&Factory, &T));
EXPECT_TRUE(Factory.Called);
Transform::TimingVec::const_iterator I = T.timing_begin();
EXPECT_GT(I->second.getProcessTime(), 0.0);
// The success of the test shouldn't depend on the order of iteration through
// timers.
llvm::sys::Path FirstFile(I->first);
if (FileA == FirstFile) {
++I;
EXPECT_EQ(FileB, llvm::sys::Path(I->first));
EXPECT_GT(I->second.getProcessTime(), 0.0);
} else if (FileB == FirstFile) {
++I;
EXPECT_EQ(FileA, llvm::sys::Path(I->first));
EXPECT_GT(I->second.getProcessTime(), 0.0);
} else {
FAIL() << "Unexpected file name " << I->first << " in timing data.";
}
++I;
EXPECT_EQ(T.timing_end(), I);
}