1 : // Copyright 2012 Google Inc. 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 "syzygy/grinder/grinder_app.h"
16 :
17 : #include "base/file_util.h"
18 : #include "base/logging.h"
19 : #include "base/string_util.h"
20 : #include "base/stringprintf.h"
21 : #include "syzygy/grinder/grinders/coverage_grinder.h"
22 : #include "syzygy/grinder/grinders/indexed_frequency_data_grinder.h"
23 : #include "syzygy/grinder/grinders/profile_grinder.h"
24 : #include "syzygy/grinder/grinders/sample_grinder.h"
25 :
26 : namespace grinder {
27 :
28 : namespace {
29 :
30 : const char kUsageFormatStr[] =
31 : "Usage: %ls <trace files> [options]\n"
32 : "\n"
33 : " A tool that parses trace files and produces summary output.\n"
34 : "\n"
35 : " In 'profile' mode it outputs KCacheGrind-compatible output files for\n"
36 : " visualization.\n"
37 : "\n"
38 : " In 'coverage' mode it outputs GCOV/LCOV-compatible or\n"
39 : " KCacheGrind-compatible output files for further processing with code\n"
40 : " coverage or line profiler visualization tools.\n"
41 : "\n"
42 : " In 'sample' mode it processes sampling profiler data and outputs heat\n"
43 : " per basic-block/function/compiland in CSV format.\n"
44 : "\n"
45 : "Required parameters\n"
46 : " --mode=<mode>\n"
47 : " The processing mode. Must be one of 'bbentry', 'branch', 'coverage',\n"
48 : " 'profile' or 'sample'.\n"
49 : "\n"
50 : "Optional parameters\n"
51 : " --output-file=<output file>\n"
52 : " The location of output file. If not specified, output is to stdout.\n"
53 : "coverage mode optional parameters\n"
54 : " --output-format=<output format>\n"
55 : " Output format must be one of 'lcov' or 'cachegrind'. Defaults to\n"
56 : " 'lcov' if not explicitly specified.\n"
57 : "profile mode optional parameters\n"
58 : " --thread-parts\n"
59 : " Aggregate and output separate parts for each thread seen in the\n"
60 : " trace files.\n"
61 : "sample mode optional parameters\n"
62 : " --aggregation-level=<level>\n"
63 : " The level of aggregation. Must be one of 'basic-block', 'function',\n"
64 : " 'compiland' or 'line'. Output is in CSV format, except for 'line'\n"
65 : " aggregation, which outputs to KCacheGrind format. Defaults to\n"
66 : " 'basic-block'.\n"
67 : " --image=<path>\n"
68 : " The path to the image for which sampling information is to be\n"
69 : " processed. If this is not specified then aggregate information\n"
70 : " will be reported for all modules encountered in the trace files.\n"
71 : " This must be specified for 'basic-block' aggregation modes, as\n"
72 : " only one module may be processed at a time in this mode.\n"
73 : "\n";
74 :
75 : } // namespace
76 :
77 E : GrinderApp::GrinderApp() : common::AppImplBase("Grinder"), mode_(kProfile) {
78 E : }
79 :
80 : void GrinderApp::PrintUsage(const base::FilePath& program,
81 E : const base::StringPiece& message) {
82 E : if (!message.empty()) {
83 E : ::fwrite(message.data(), 1, message.length(), out());
84 E : ::fprintf(out(), "\n\n");
85 : }
86 :
87 E : ::fprintf(out(), kUsageFormatStr, program.BaseName().value().c_str());
88 E : }
89 :
90 E : bool GrinderApp::ParseCommandLine(const CommandLine* command_line) {
91 E : DCHECK(command_line != NULL);
92 :
93 E : CommandLine::StringVector args = command_line->GetArgs();
94 E : if (args.empty()) {
95 : PrintUsage(command_line->GetProgram(),
96 E : "You must provide at least one trace file.");
97 E : return false;
98 : }
99 :
100 E : if (!command_line->HasSwitch("mode")) {
101 : PrintUsage(command_line->GetProgram(),
102 E : "You must specify the processing mode.");
103 E : return false;
104 : }
105 :
106 E : for (size_t i = 0; i < args.size(); ++i) {
107 E : if (!AppendMatchingPaths(base::FilePath(args[i]), &trace_files_)) {
108 : PrintUsage(command_line->GetProgram(),
109 i : base::StringPrintf("No such file '%ws'.", args[i].c_str()));
110 i : return false;
111 : }
112 E : }
113 :
114 : // Parse the processing mode.
115 E : std::string mode = command_line->GetSwitchValueASCII("mode");
116 E : if (LowerCaseEqualsASCII(mode, "profile")) {
117 E : mode_ = kProfile;
118 E : grinder_.reset(new grinders::ProfileGrinder());
119 E : } else if (LowerCaseEqualsASCII(mode, "coverage")) {
120 E : mode_ = kCoverage;
121 E : grinder_.reset(new grinders::CoverageGrinder());
122 E : } else if (LowerCaseEqualsASCII(mode, "bbentry")) {
123 E : mode_ = kBasicBlockEntry;
124 E : grinder_.reset(new grinders::IndexedFrequencyDataGrinder());
125 E : } else if (LowerCaseEqualsASCII(mode, "branch")) {
126 i : mode_ = kIndexedFrequencyData;
127 i : grinder_.reset(new grinders::IndexedFrequencyDataGrinder());
128 E : } else if (LowerCaseEqualsASCII(mode, "sample")) {
129 E : mode_ = kSample;
130 E : grinder_.reset(new grinders::SampleGrinder());
131 E : } else {
132 : PrintUsage(command_line->GetProgram(),
133 i : base::StringPrintf("Unknown mode: %s.", mode.c_str()));
134 i : return false;
135 : }
136 E : DCHECK(grinder_.get() != NULL);
137 :
138 : // Parse the command-line for the grinder.
139 E : if (!grinder_->ParseCommandLine(command_line)) {
140 : PrintUsage(command_line->GetProgram(),
141 : base::StringPrintf("Failed to parse %s parameters.",
142 i : mode.c_str()));
143 i : return false;
144 : }
145 :
146 E : output_file_ = command_line->GetSwitchValuePath("output-file");
147 :
148 E : return true;
149 E : }
150 :
151 E : int GrinderApp::Run() {
152 E : DCHECK(grinder_.get() != NULL);
153 :
154 E : trace::parser::Parser parser;
155 E : grinder_->SetParser(&parser);
156 E : if (!parser.Init(grinder_.get()))
157 i : return 1;
158 :
159 : // Open the input files.
160 E : for (size_t i = 0; i < trace_files_.size(); ++i) {
161 E : if (!parser.OpenTraceFile(trace_files_[i])) {
162 i : LOG(ERROR) << "Unable to open trace file \'"
163 : << trace_files_[i].value() << "'";
164 i : return 1;
165 : }
166 E : }
167 :
168 : // Open the output file. We do this early so as to fail before processing
169 : // the logs if the output is not able to be opened.
170 E : FILE* output = out();
171 E : file_util::ScopedFILE auto_close;
172 E : if (!output_file_.empty()) {
173 E : output = file_util::OpenFile(output_file_, "w");
174 E : if (output == NULL) {
175 i : LOG(ERROR) << "Unable to create output file \'"
176 : << output_file_.value() << "'";
177 i : return 1;
178 : }
179 :
180 E : auto_close.reset(output);
181 : }
182 :
183 E : LOG(INFO) << "Parsing trace files.";
184 E : if (!parser.Consume()) {
185 i : LOG(ERROR) << "Error parsing trace files.";
186 i : return 1;
187 : }
188 :
189 E : LOG(INFO) << "Aggregating data.";
190 E : if (!grinder_->Grind()) {
191 i : LOG(ERROR) << "Failed to grind data.";
192 i : return 1;
193 : }
194 :
195 E : std::wstring output_name(L"stdout");
196 E : if (!output_file_.empty())
197 E : output_name = base::StringPrintf(L"\"%ls\"", output_file_.value().c_str());
198 E : LOG(INFO) << "Writing output to " << output_name << ".";
199 E : DCHECK(output != NULL);
200 E : if (!grinder_->OutputData(output)) {
201 i : LOG(ERROR) << "Failed to output data.";
202 i : return 1;
203 : }
204 :
205 E : return 0;
206 E : }
207 :
208 E : void GrinderApp::TearDown() {
209 : // Release the grinder so it has a chance to clean up before COM goes away.
210 E : grinder_.reset();
211 E : }
212 :
213 : } // namespace grinder
|