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/strings/string_util.h"
20 : #include "base/strings/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 : GrinderApp::GrinderApp()
78 E : : application::AppImplBase("Grinder"), mode_(kProfile) {
79 E : }
80 :
81 : void GrinderApp::PrintUsage(const base::FilePath& program,
82 E : const base::StringPiece& message) {
83 E : if (!message.empty()) {
84 E : ::fwrite(message.data(), 1, message.length(), out());
85 E : ::fprintf(out(), "\n\n");
86 : }
87 :
88 E : ::fprintf(out(), kUsageFormatStr, program.BaseName().value().c_str());
89 E : }
90 :
91 E : bool GrinderApp::ParseCommandLine(const CommandLine* command_line) {
92 E : DCHECK(command_line != NULL);
93 :
94 E : CommandLine::StringVector args = command_line->GetArgs();
95 E : if (args.empty()) {
96 : PrintUsage(command_line->GetProgram(),
97 E : "You must provide at least one trace file.");
98 E : return false;
99 : }
100 :
101 E : if (!command_line->HasSwitch("mode")) {
102 : PrintUsage(command_line->GetProgram(),
103 E : "You must specify the processing mode.");
104 E : return false;
105 : }
106 :
107 E : for (size_t i = 0; i < args.size(); ++i) {
108 E : if (!AppendMatchingPaths(base::FilePath(args[i]), &trace_files_)) {
109 : PrintUsage(command_line->GetProgram(),
110 i : base::StringPrintf("No such file '%ws'.", args[i].c_str()));
111 i : return false;
112 : }
113 E : }
114 :
115 : // Parse the processing mode.
116 E : std::string mode = command_line->GetSwitchValueASCII("mode");
117 E : if (LowerCaseEqualsASCII(mode, "profile")) {
118 E : mode_ = kProfile;
119 E : grinder_.reset(new grinders::ProfileGrinder());
120 E : } else if (LowerCaseEqualsASCII(mode, "coverage")) {
121 E : mode_ = kCoverage;
122 E : grinder_.reset(new grinders::CoverageGrinder());
123 E : } else if (LowerCaseEqualsASCII(mode, "bbentry")) {
124 E : mode_ = kBasicBlockEntry;
125 E : grinder_.reset(new grinders::IndexedFrequencyDataGrinder());
126 E : } else if (LowerCaseEqualsASCII(mode, "branch")) {
127 i : mode_ = kIndexedFrequencyData;
128 i : grinder_.reset(new grinders::IndexedFrequencyDataGrinder());
129 E : } else if (LowerCaseEqualsASCII(mode, "sample")) {
130 E : mode_ = kSample;
131 E : grinder_.reset(new grinders::SampleGrinder());
132 E : } else {
133 : PrintUsage(command_line->GetProgram(),
134 i : base::StringPrintf("Unknown mode: %s.", mode.c_str()));
135 i : return false;
136 : }
137 E : DCHECK(grinder_.get() != NULL);
138 :
139 : // Parse the command-line for the grinder.
140 E : if (!grinder_->ParseCommandLine(command_line)) {
141 : PrintUsage(command_line->GetProgram(),
142 : base::StringPrintf("Failed to parse %s parameters.",
143 i : mode.c_str()));
144 i : return false;
145 : }
146 :
147 E : output_file_ = command_line->GetSwitchValuePath("output-file");
148 :
149 E : return true;
150 E : }
151 :
152 E : int GrinderApp::Run() {
153 E : DCHECK(grinder_.get() != NULL);
154 :
155 E : trace::parser::Parser parser;
156 E : grinder_->SetParser(&parser);
157 E : if (!parser.Init(grinder_.get()))
158 i : return 1;
159 :
160 : // Open the input files.
161 E : for (size_t i = 0; i < trace_files_.size(); ++i) {
162 E : if (!parser.OpenTraceFile(trace_files_[i])) {
163 i : LOG(ERROR) << "Unable to open trace file \'"
164 : << trace_files_[i].value() << "'";
165 i : return 1;
166 : }
167 E : }
168 :
169 : // Open the output file. We do this early so as to fail before processing
170 : // the logs if the output is not able to be opened.
171 E : FILE* output = out();
172 E : base::ScopedFILE auto_close;
173 E : if (!output_file_.empty()) {
174 E : output = base::OpenFile(output_file_, "w");
175 E : if (output == NULL) {
176 i : LOG(ERROR) << "Unable to create output file \'"
177 : << output_file_.value() << "'";
178 i : return 1;
179 : }
180 :
181 E : auto_close.reset(output);
182 : }
183 :
184 E : LOG(INFO) << "Parsing trace files.";
185 E : if (!parser.Consume()) {
186 i : LOG(ERROR) << "Error parsing trace files.";
187 i : return 1;
188 : }
189 :
190 E : LOG(INFO) << "Aggregating data.";
191 E : if (!grinder_->Grind()) {
192 i : LOG(ERROR) << "Failed to grind data.";
193 i : return 1;
194 : }
195 :
196 E : std::wstring output_name(L"stdout");
197 E : if (!output_file_.empty())
198 E : output_name = base::StringPrintf(L"\"%ls\"", output_file_.value().c_str());
199 E : LOG(INFO) << "Writing output to " << output_name << ".";
200 E : DCHECK(output != NULL);
201 E : if (!grinder_->OutputData(output)) {
202 i : LOG(ERROR) << "Failed to output data.";
203 i : return 1;
204 : }
205 :
206 E : return 0;
207 E : }
208 :
209 E : void GrinderApp::TearDown() {
210 : // Release the grinder so it has a chance to clean up before COM goes away.
211 E : grinder_.reset();
212 E : }
213 :
214 : } // namespace grinder
|