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 <iostream>
16 :
17 : #include "base/at_exit.h"
18 : #include "base/command_line.h"
19 : #include "base/file_path.h"
20 : #include "base/logging.h"
21 : #include "base/path_service.h"
22 : #include "base/process_util.h"
23 : #include "base/string_number_conversions.h"
24 : #include "base/string_piece.h"
25 : #include "base/string_util.h"
26 : #include "base/utf_string_conversions.h"
27 : #include "base/memory/scoped_ptr.h"
28 : #include "base/threading/thread.h"
29 : #include "sawbuck/common/com_utils.h"
30 : #include "syzygy/trace/protocol/call_trace_defs.h"
31 : #include "syzygy/trace/rpc/rpc_helpers.h"
32 : #include "syzygy/trace/service/service.h"
33 : #include "syzygy/trace/service/service_rpc_impl.h"
34 : #include "syzygy/trace/service/trace_file_writer_factory.h"
35 :
36 : namespace trace {
37 : namespace service {
38 : namespace {
39 :
40 : using ::trace::client::CreateRpcBinding;
41 : using ::trace::client::InvokeRpc;
42 :
43 : // Minimum buffer size to allow (1 MB).
44 : const int kMinBufferSize = 1024 * 1024;
45 :
46 : // Minumum number of buffers to allocate.
47 : const int kMinBuffers = 16;
48 :
49 : // A static location to which the current instance id can be saved. We
50 : // persist it here so that OnConsoleCtrl can have access to the instance
51 : // id when it is invoked on the signal handler thread.
52 : wchar_t saved_instance_id[16] = {0};
53 :
54 : // Forward declaration.
55 : bool StopService(const base::StringPiece16& instance_id);
56 :
57 : // Handler function to be called on exit signals (Ctrl-C, TERM, etc...).
58 i : BOOL WINAPI OnConsoleCtrl(DWORD ctrl_type) {
59 i : if (ctrl_type != CTRL_LOGOFF_EVENT) {
60 i : StopService(saved_instance_id);
61 i : return TRUE;
62 : }
63 i : return FALSE;
64 i : }
65 :
66 : const char* const kInstanceId = "instance-id";
67 :
68 : const char kUsage[] =
69 : "Usage: call_trace_service ACTION [OPTIONS]\n"
70 : "\n"
71 : "Actions:\n"
72 : " start Start the call trace service. This causes an\n"
73 : " instance of the service to be launched as a\n"
74 : " foreground process.\n"
75 : " spawn Spawns an instance of the call trace service, waits\n"
76 : " for it to be ready, and returns. The call trace\n"
77 : " service continues running in the background.\n"
78 : " stop Stop the call trace service.\n"
79 : "\n"
80 : "Options:\n"
81 : " --help Show this help message.\n"
82 : " --trace-dir=PATH The directory in which to write the trace files.\n"
83 : " --buffer-size=NUM The size (in bytes) of each buffer to allocate.\n"
84 : " --num-incremental-buffers=NUM\n"
85 : " The number of buffers by which to grow the buffer\n"
86 : " pool each time the client exhausts its available\n"
87 : " buffer space.\n"
88 : " --enable-exits Enable exit tracing (off by default).\n"
89 : " --verbose Increase the logging verbosity to also include\n"
90 : " debug-level information.\n"
91 : " --instance-id=ID A unique identifier to use for the RPC endoint.\n"
92 : " This allows multiple instances of the service to\n"
93 : " run concurrently. By default this is empty.\n"
94 : "\n";
95 :
96 i : int Usage() {
97 i : std::cout << kUsage;
98 i : return 1;
99 i : }
100 :
101 E : bool GetInstanceId(const CommandLine* cmd_line, std::wstring* id) {
102 E : DCHECK(cmd_line != NULL);
103 E : DCHECK(id != NULL);
104 :
105 : // If not specified, this defaults to the empty string.
106 E : *id = cmd_line->GetSwitchValueNative(kInstanceId);
107 :
108 E : const size_t kMaxLength = arraysize(saved_instance_id) - 1;
109 E : if (id->length() > kMaxLength) {
110 i : LOG(ERROR) << "The instance id '" << *id << "' is too long. "
111 : << "The max length is " << kMaxLength << " characters.";
112 i : return false;
113 : }
114 :
115 E : return true;
116 E : }
117 :
118 E : bool RunService(const CommandLine* cmd_line) {
119 E : DCHECK(cmd_line != NULL);
120 :
121 E : base::Thread writer_thread("trace-file-writer");
122 : if (!writer_thread.StartWithOptions(
123 E : base::Thread::Options(MessageLoop::TYPE_IO, 0))) {
124 i : LOG(ERROR) << "Failed to start call trace service writer thread.";
125 i : return 1;
126 : }
127 :
128 E : MessageLoop* message_loop = writer_thread.message_loop();
129 E : TraceFileWriterFactory trace_file_writer_factory(message_loop);
130 E : Service call_trace_service(&trace_file_writer_factory);
131 E : RpcServiceInstanceManager rpc_instance(&call_trace_service);
132 :
133 : // Get/set the instance id.
134 E : std::wstring instance_id;
135 E : if (!GetInstanceId(cmd_line, &instance_id))
136 i : return false;
137 :
138 E : call_trace_service.set_instance_id(instance_id);
139 : base::wcslcpy(saved_instance_id,
140 : instance_id.c_str(),
141 E : arraysize(saved_instance_id));
142 :
143 : // Set up the trace directory.
144 E : FilePath trace_directory(cmd_line->GetSwitchValuePath("trace-dir"));
145 E : if (trace_directory.empty())
146 E : trace_directory = FilePath(L".");
147 E : if (!trace_file_writer_factory.SetTraceFileDirectory(trace_directory))
148 i : return false;
149 :
150 : // Setup the buffer size.
151 E : std::wstring buffer_size_str(cmd_line->GetSwitchValueNative("buffer-size"));
152 E : if (!buffer_size_str.empty()) {
153 i : int num = 0;
154 i : if (!base::StringToInt(buffer_size_str, &num) || num < kMinBufferSize) {
155 i : LOG(ERROR) << "Buffer size is too small (<" << kMinBufferSize << ").";
156 i : return false;
157 : }
158 i : call_trace_service.set_buffer_size_in_bytes(num);
159 : }
160 :
161 E : if (cmd_line->HasSwitch("enable-exits")) {
162 i : call_trace_service.set_flags(TRACE_FLAG_ENTER | TRACE_FLAG_EXIT);
163 : }
164 :
165 : // Setup the number of incremental buffers
166 : std::wstring buffers_str(
167 E : cmd_line->GetSwitchValueNative("num-incremental-buffers"));
168 E : if (!buffers_str.empty()) {
169 i : int num = 0;
170 i : if (!base::StringToInt(buffers_str, &num) || num < kMinBuffers) {
171 i : LOG(ERROR) << "Number of incremental buffers is too small (<"
172 : << kMinBuffers << ").";
173 i : return false;
174 : }
175 i : call_trace_service.set_num_incremental_buffers(num);
176 : }
177 :
178 : // Setup the handler for exit signals.
179 E : if (!SetConsoleCtrlHandler(&OnConsoleCtrl, TRUE)) {
180 i : DWORD error = ::GetLastError();
181 i : LOG(ERROR) << "Failed to register shutdown handler: "
182 : << com::LogWe(error) << ".";
183 i : return false;
184 : }
185 :
186 : // Run the service in blocking mode. This will not return until the service
187 : // has been externally stopped.
188 E : call_trace_service.Start(false);
189 :
190 : // We no longer need to look out for exit signals.
191 E : SetConsoleCtrlHandler(&OnConsoleCtrl, FALSE);
192 :
193 : // The call trace service will be stopped on destruction.
194 E : return true;
195 E : }
196 :
197 i : bool SpawnService(const CommandLine* cmd_line) {
198 : // Get the path to ourselves.
199 i : FilePath self_path;
200 i : PathService::Get(base::FILE_EXE, &self_path);
201 :
202 : // Build a command line for starting a new instance of the service.
203 i : CommandLine service_cmd(self_path);
204 i : service_cmd.AppendArg("start");
205 :
206 : // Copy over any other switches.
207 : CommandLine::SwitchMap::const_iterator it =
208 i : cmd_line->GetSwitches().begin();
209 i : for (; it != cmd_line->GetSwitches().end(); ++it)
210 i : service_cmd.AppendSwitchNative(it->first, it->second);
211 :
212 : // Get the instance id.
213 i : std::wstring instance_id;
214 i : if (!GetInstanceId(cmd_line, &instance_id))
215 i : return false;
216 :
217 : // Launch a new process in the background.
218 i : LOG(INFO) << "Launching background call trace service with instance ID \""
219 : << instance_id << "\".";
220 : base::ProcessHandle service_process;
221 i : base::LaunchOptions options;
222 i : options.start_hidden = true;
223 i : if (!base::LaunchProcess(service_cmd, options, &service_process)) {
224 i : LOG(ERROR) << "Failed to launch process.";
225 i : return false;
226 : }
227 i : DCHECK_NE(base::kNullProcessHandle, service_process);
228 :
229 : // Get the name of the event that will be signalled when the service is up
230 : // and running.
231 i : std::wstring event_name;
232 i : ::GetSyzygyCallTraceRpcEventName(instance_id, &event_name);
233 : base::win::ScopedHandle rpc_event(
234 i : ::CreateEvent(NULL, TRUE, FALSE, event_name.c_str()));
235 i : if (!rpc_event.IsValid()) {
236 i : LOG(ERROR) << "Unable to create RPC event for instance id \""
237 : << instance_id << "\".";
238 i : return false;
239 : }
240 :
241 : // We wait on both the RPC event and the process, as if the process fails for
242 : // any reason, it'll exit and its handle will become signalled.
243 i : HANDLE handles[] = { rpc_event.Get(), service_process };
244 : if (::WaitForMultipleObjects(arraysize(handles),
245 : handles,
246 : FALSE,
247 i : INFINITE) != WAIT_OBJECT_0) {
248 i : LOG(ERROR) << "The spawned call trace service exited in error.";
249 i : return false;
250 : }
251 :
252 i : LOG(INFO) << "Background call trace service with instance ID \""
253 : << instance_id << "\" is ready.";
254 :
255 i : return true;
256 i : }
257 :
258 E : bool StopService(const base::StringPiece16& instance_id) {
259 E : std::wstring protocol;
260 E : std::wstring endpoint;
261 :
262 E : ::GetSyzygyCallTraceRpcProtocol(&protocol);
263 E : ::GetSyzygyCallTraceRpcEndpoint(instance_id, &endpoint);
264 :
265 E : LOG(INFO) << "Stopping call trace logging service instance at '"
266 : << endpoint << "' via " << protocol << '.';
267 :
268 E : handle_t binding = NULL;
269 E : if (!CreateRpcBinding(protocol, endpoint, &binding)) {
270 i : LOG(ERROR) << "Failed to connect to call trace logging service.";
271 i : return false;
272 : }
273 :
274 E : if (!InvokeRpc(CallTraceClient_Stop, binding).succeeded()) {
275 i : LOG(ERROR) << "Failed to stop call trace logging service.";
276 i : return false;
277 : }
278 :
279 : // TODO(rogerm): It would be nice to make this blocking until the
280 : // service actually shuts down. Perhaps with retries on the stop
281 : // request.
282 E : LOG(INFO) << "Call trace service shutdown has been requested.";
283 E : return true;
284 E : }
285 :
286 E : extern "C" int main(int argc, char** argv) {
287 E : base::AtExitManager at_exit_manager;
288 E : CommandLine::Init(argc, argv);
289 E : const int kVlogLevelVerbose = -2;
290 : if (!logging::InitLogging(
291 : L"",
292 : logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG,
293 : logging::DONT_LOCK_LOG_FILE,
294 : logging::APPEND_TO_OLD_LOG_FILE,
295 E : logging::ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS)) {
296 i : return 1;
297 : }
298 :
299 : // TODO(rogerm): Turn on ETW logging as well.
300 :
301 E : CommandLine* cmd_line = CommandLine::ForCurrentProcess();
302 E : DCHECK(cmd_line != NULL);
303 :
304 E : if (cmd_line->HasSwitch("verbose")) {
305 E : logging::SetMinLogLevel(kVlogLevelVerbose);
306 : }
307 :
308 E : if (cmd_line->HasSwitch("help") || cmd_line->GetArgs().size() < 1) {
309 i : return Usage();
310 : }
311 :
312 E : if (LowerCaseEqualsASCII(cmd_line->GetArgs()[0], "stop")) {
313 E : std::wstring id;
314 E : return (GetInstanceId(cmd_line, &id) && StopService(id)) ? 0 : 1;
315 : }
316 :
317 E : if (LowerCaseEqualsASCII(cmd_line->GetArgs()[0], "start")) {
318 E : return RunService(cmd_line) ? 0 : 1;
319 : }
320 :
321 i : if (LowerCaseEqualsASCII(cmd_line->GetArgs()[0], "spawn")) {
322 i : return SpawnService(cmd_line) ? 0 : 1;
323 : }
324 :
325 i : return Usage();
326 E : }
327 :
328 : } // namespace
329 : } // namespace service
330 : } // namespace trace
|