Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
* vim: set ts=8 sts=2 et sw=2 tw=80:
3
* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
/* Profiling-related API */
8
9
#include "builtin/Profilers.h"
10
11
#include "mozilla/Compiler.h"
12
#include "mozilla/Sprintf.h"
13
14
#include <stdarg.h>
15
16
#ifdef MOZ_CALLGRIND
17
# include <valgrind/callgrind.h>
18
#endif
19
20
#ifdef __APPLE__
21
# ifdef MOZ_INSTRUMENTS
22
# include "devtools/Instruments.h"
23
# endif
24
#endif
25
26
#ifdef XP_WIN
27
# include <process.h>
28
# define getpid _getpid
29
#endif
30
31
#include "js/CharacterEncoding.h"
32
#include "js/PropertySpec.h"
33
#include "js/Utility.h"
34
#include "util/Text.h"
35
#include "vm/Probes.h"
36
37
#include "vm/JSContext-inl.h"
38
39
using namespace js;
40
41
using mozilla::ArrayLength;
42
43
/* Thread-unsafe error management */
44
45
static char gLastError[2000];
46
47
#if defined(__APPLE__) || defined(__linux__) || defined(MOZ_CALLGRIND)
48
static void MOZ_FORMAT_PRINTF(1, 2) UnsafeError(const char* format, ...) {
49
va_list args;
50
va_start(args, format);
51
(void)VsprintfLiteral(gLastError, format, args);
52
va_end(args);
53
}
54
#endif
55
56
JS_PUBLIC_API const char* JS_UnsafeGetLastProfilingError() {
57
return gLastError;
58
}
59
60
#ifdef __APPLE__
61
static bool StartOSXProfiling(const char* profileName, pid_t pid) {
62
bool ok = true;
63
const char* profiler = nullptr;
64
# ifdef MOZ_INSTRUMENTS
65
ok = Instruments::Start(pid);
66
profiler = "Instruments";
67
# endif
68
if (!ok) {
69
if (profileName) {
70
UnsafeError("Failed to start %s for %s", profiler, profileName);
71
} else {
72
UnsafeError("Failed to start %s", profiler);
73
}
74
return false;
75
}
76
return true;
77
}
78
#endif
79
80
JS_PUBLIC_API bool JS_StartProfiling(const char* profileName, pid_t pid) {
81
bool ok = true;
82
#ifdef __APPLE__
83
ok = StartOSXProfiling(profileName, pid);
84
#endif
85
#ifdef __linux__
86
if (!js_StartPerf()) {
87
ok = false;
88
}
89
#endif
90
return ok;
91
}
92
93
JS_PUBLIC_API bool JS_StopProfiling(const char* profileName) {
94
bool ok = true;
95
#ifdef __APPLE__
96
# ifdef MOZ_INSTRUMENTS
97
Instruments::Stop(profileName);
98
# endif
99
#endif
100
#ifdef __linux__
101
if (!js_StopPerf()) {
102
ok = false;
103
}
104
#endif
105
return ok;
106
}
107
108
/*
109
* Start or stop whatever platform- and configuration-specific profiling
110
* backends are available.
111
*/
112
static bool ControlProfilers(bool toState) {
113
bool ok = true;
114
115
if (!probes::ProfilingActive && toState) {
116
#ifdef __APPLE__
117
# if defined(MOZ_INSTRUMENTS)
118
const char* profiler;
119
# ifdef MOZ_INSTRUMENTS
120
ok = Instruments::Resume();
121
profiler = "Instruments";
122
# endif
123
if (!ok) {
124
UnsafeError("Failed to start %s", profiler);
125
}
126
# endif
127
#endif
128
#ifdef MOZ_CALLGRIND
129
if (!js_StartCallgrind()) {
130
UnsafeError("Failed to start Callgrind");
131
ok = false;
132
}
133
#endif
134
} else if (probes::ProfilingActive && !toState) {
135
#ifdef __APPLE__
136
# ifdef MOZ_INSTRUMENTS
137
Instruments::Pause();
138
# endif
139
#endif
140
#ifdef MOZ_CALLGRIND
141
if (!js_StopCallgrind()) {
142
UnsafeError("failed to stop Callgrind");
143
ok = false;
144
}
145
#endif
146
}
147
148
probes::ProfilingActive = toState;
149
150
return ok;
151
}
152
153
/*
154
* Pause/resume whatever profiling mechanism is currently compiled
155
* in, if applicable. This will not affect things like dtrace.
156
*
157
* Do not mix calls to these APIs with calls to the individual
158
* profilers' pause/resume functions, because only overall state is
159
* tracked, not the state of each profiler.
160
*/
161
JS_PUBLIC_API bool JS_PauseProfilers(const char* profileName) {
162
return ControlProfilers(false);
163
}
164
165
JS_PUBLIC_API bool JS_ResumeProfilers(const char* profileName) {
166
return ControlProfilers(true);
167
}
168
169
JS_PUBLIC_API bool JS_DumpProfile(const char* outfile,
170
const char* profileName) {
171
bool ok = true;
172
#ifdef MOZ_CALLGRIND
173
ok = js_DumpCallgrind(outfile);
174
#endif
175
return ok;
176
}
177
178
#ifdef MOZ_PROFILING
179
180
static UniqueChars RequiredStringArg(JSContext* cx, const CallArgs& args,
181
size_t argi, const char* caller) {
182
if (args.length() <= argi) {
183
JS_ReportErrorASCII(cx, "%s: not enough arguments", caller);
184
return nullptr;
185
}
186
187
if (!args[argi].isString()) {
188
JS_ReportErrorASCII(cx, "%s: invalid arguments (string expected)", caller);
189
return nullptr;
190
}
191
192
return JS_EncodeStringToLatin1(cx, args[argi].toString());
193
}
194
195
static bool StartProfiling(JSContext* cx, unsigned argc, Value* vp) {
196
CallArgs args = CallArgsFromVp(argc, vp);
197
if (args.length() == 0) {
198
args.rval().setBoolean(JS_StartProfiling(nullptr, getpid()));
199
return true;
200
}
201
202
UniqueChars profileName = RequiredStringArg(cx, args, 0, "startProfiling");
203
if (!profileName) {
204
return false;
205
}
206
207
if (args.length() == 1) {
208
args.rval().setBoolean(JS_StartProfiling(profileName.get(), getpid()));
209
return true;
210
}
211
212
if (!args[1].isInt32()) {
213
JS_ReportErrorASCII(cx, "startProfiling: invalid arguments (int expected)");
214
return false;
215
}
216
pid_t pid = static_cast<pid_t>(args[1].toInt32());
217
args.rval().setBoolean(JS_StartProfiling(profileName.get(), pid));
218
return true;
219
}
220
221
static bool StopProfiling(JSContext* cx, unsigned argc, Value* vp) {
222
CallArgs args = CallArgsFromVp(argc, vp);
223
if (args.length() == 0) {
224
args.rval().setBoolean(JS_StopProfiling(nullptr));
225
return true;
226
}
227
228
UniqueChars profileName = RequiredStringArg(cx, args, 0, "stopProfiling");
229
if (!profileName) {
230
return false;
231
}
232
args.rval().setBoolean(JS_StopProfiling(profileName.get()));
233
return true;
234
}
235
236
static bool PauseProfilers(JSContext* cx, unsigned argc, Value* vp) {
237
CallArgs args = CallArgsFromVp(argc, vp);
238
if (args.length() == 0) {
239
args.rval().setBoolean(JS_PauseProfilers(nullptr));
240
return true;
241
}
242
243
UniqueChars profileName = RequiredStringArg(cx, args, 0, "pauseProfiling");
244
if (!profileName) {
245
return false;
246
}
247
args.rval().setBoolean(JS_PauseProfilers(profileName.get()));
248
return true;
249
}
250
251
static bool ResumeProfilers(JSContext* cx, unsigned argc, Value* vp) {
252
CallArgs args = CallArgsFromVp(argc, vp);
253
if (args.length() == 0) {
254
args.rval().setBoolean(JS_ResumeProfilers(nullptr));
255
return true;
256
}
257
258
UniqueChars profileName = RequiredStringArg(cx, args, 0, "resumeProfiling");
259
if (!profileName) {
260
return false;
261
}
262
args.rval().setBoolean(JS_ResumeProfilers(profileName.get()));
263
return true;
264
}
265
266
/* Usage: DumpProfile([filename[, profileName]]) */
267
static bool DumpProfile(JSContext* cx, unsigned argc, Value* vp) {
268
bool ret;
269
CallArgs args = CallArgsFromVp(argc, vp);
270
if (args.length() == 0) {
271
ret = JS_DumpProfile(nullptr, nullptr);
272
} else {
273
UniqueChars filename = RequiredStringArg(cx, args, 0, "dumpProfile");
274
if (!filename) {
275
return false;
276
}
277
278
if (args.length() == 1) {
279
ret = JS_DumpProfile(filename.get(), nullptr);
280
} else {
281
UniqueChars profileName = RequiredStringArg(cx, args, 1, "dumpProfile");
282
if (!profileName) {
283
return false;
284
}
285
286
ret = JS_DumpProfile(filename.get(), profileName.get());
287
}
288
}
289
290
args.rval().setBoolean(ret);
291
return true;
292
}
293
294
static bool GetMaxGCPauseSinceClear(JSContext* cx, unsigned argc, Value* vp) {
295
CallArgs args = CallArgsFromVp(argc, vp);
296
args.rval().setNumber(
297
cx->runtime()->gc.stats().getMaxGCPauseSinceClear().ToMicroseconds());
298
return true;
299
}
300
301
static bool ClearMaxGCPauseAccumulator(JSContext* cx, unsigned argc,
302
Value* vp) {
303
CallArgs args = CallArgsFromVp(argc, vp);
304
args.rval().setNumber(
305
cx->runtime()->gc.stats().clearMaxGCPauseAccumulator().ToMicroseconds());
306
return true;
307
}
308
309
# if defined(MOZ_INSTRUMENTS)
310
311
static bool IgnoreAndReturnTrue(JSContext* cx, unsigned argc, Value* vp) {
312
CallArgs args = CallArgsFromVp(argc, vp);
313
args.rval().setBoolean(true);
314
return true;
315
}
316
317
# endif
318
319
# ifdef MOZ_CALLGRIND
320
static bool StartCallgrind(JSContext* cx, unsigned argc, Value* vp) {
321
CallArgs args = CallArgsFromVp(argc, vp);
322
args.rval().setBoolean(js_StartCallgrind());
323
return true;
324
}
325
326
static bool StopCallgrind(JSContext* cx, unsigned argc, Value* vp) {
327
CallArgs args = CallArgsFromVp(argc, vp);
328
args.rval().setBoolean(js_StopCallgrind());
329
return true;
330
}
331
332
static bool DumpCallgrind(JSContext* cx, unsigned argc, Value* vp) {
333
CallArgs args = CallArgsFromVp(argc, vp);
334
if (args.length() == 0) {
335
args.rval().setBoolean(js_DumpCallgrind(nullptr));
336
return true;
337
}
338
339
UniqueChars outFile = RequiredStringArg(cx, args, 0, "dumpCallgrind");
340
if (!outFile) {
341
return false;
342
}
343
344
args.rval().setBoolean(js_DumpCallgrind(outFile.get()));
345
return true;
346
}
347
# endif
348
349
static const JSFunctionSpec profiling_functions[] = {
350
JS_FN("startProfiling", StartProfiling, 1, 0),
351
JS_FN("stopProfiling", StopProfiling, 1, 0),
352
JS_FN("pauseProfilers", PauseProfilers, 1, 0),
353
JS_FN("resumeProfilers", ResumeProfilers, 1, 0),
354
JS_FN("dumpProfile", DumpProfile, 2, 0),
355
JS_FN("getMaxGCPauseSinceClear", GetMaxGCPauseSinceClear, 0, 0),
356
JS_FN("clearMaxGCPauseAccumulator", ClearMaxGCPauseAccumulator, 0, 0),
357
# if defined(MOZ_INSTRUMENTS)
358
/* Keep users of the old shark API happy. */
359
JS_FN("connectShark", IgnoreAndReturnTrue, 0, 0),
360
JS_FN("disconnectShark", IgnoreAndReturnTrue, 0, 0),
361
JS_FN("startShark", StartProfiling, 0, 0),
362
JS_FN("stopShark", StopProfiling, 0, 0),
363
# endif
364
# ifdef MOZ_CALLGRIND
365
JS_FN("startCallgrind", StartCallgrind, 0, 0),
366
JS_FN("stopCallgrind", StopCallgrind, 0, 0),
367
JS_FN("dumpCallgrind", DumpCallgrind, 1, 0),
368
# endif
369
JS_FS_END};
370
371
#endif
372
373
JS_PUBLIC_API bool JS_DefineProfilingFunctions(JSContext* cx,
374
HandleObject obj) {
375
cx->check(obj);
376
#ifdef MOZ_PROFILING
377
return JS_DefineFunctions(cx, obj, profiling_functions);
378
#else
379
return true;
380
#endif
381
}
382
383
#ifdef MOZ_CALLGRIND
384
385
/* Wrapper for various macros to stop warnings coming from their expansions. */
386
# if defined(__clang__)
387
# define JS_SILENCE_UNUSED_VALUE_IN_EXPR(expr) \
388
JS_BEGIN_MACRO \
389
_Pragma("clang diagnostic push") /* If these _Pragmas cause warnings \
390
for you, try disabling ccache. */ \
391
_Pragma("clang diagnostic ignored \"-Wunused-value\"") { \
392
expr; \
393
} \
394
_Pragma("clang diagnostic pop") \
395
JS_END_MACRO
396
# elif MOZ_IS_GCC
397
398
# define JS_SILENCE_UNUSED_VALUE_IN_EXPR(expr) \
399
JS_BEGIN_MACRO \
400
_Pragma("GCC diagnostic push") \
401
_Pragma("GCC diagnostic ignored \"-Wunused-but-set-variable\"") \
402
expr; \
403
_Pragma("GCC diagnostic pop") \
404
JS_END_MACRO
405
# endif
406
407
# if !defined(JS_SILENCE_UNUSED_VALUE_IN_EXPR)
408
# define JS_SILENCE_UNUSED_VALUE_IN_EXPR(expr) \
409
JS_BEGIN_MACRO \
410
expr; \
411
JS_END_MACRO
412
# endif
413
414
JS_FRIEND_API bool js_StartCallgrind() {
415
JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_START_INSTRUMENTATION);
416
JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_ZERO_STATS);
417
return true;
418
}
419
420
JS_FRIEND_API bool js_StopCallgrind() {
421
JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_STOP_INSTRUMENTATION);
422
return true;
423
}
424
425
JS_FRIEND_API bool js_DumpCallgrind(const char* outfile) {
426
if (outfile) {
427
JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_DUMP_STATS_AT(outfile));
428
} else {
429
JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_DUMP_STATS);
430
}
431
432
return true;
433
}
434
435
#endif /* MOZ_CALLGRIND */
436
437
#ifdef __linux__
438
439
/*
440
* Code for starting and stopping |perf|, the Linux profiler.
441
*
442
* Output from profiling is written to mozperf.data in your cwd.
443
*
444
* To enable, set MOZ_PROFILE_WITH_PERF=1 in your environment.
445
*
446
* To pass additional parameters to |perf record|, provide them in the
447
* MOZ_PROFILE_PERF_FLAGS environment variable. If this variable does not
448
* exist, we default it to "--call-graph". (If you don't want --call-graph but
449
* don't want to pass any other args, define MOZ_PROFILE_PERF_FLAGS to the empty
450
* string.)
451
*
452
* If you include --pid or --output in MOZ_PROFILE_PERF_FLAGS, you're just
453
* asking for trouble.
454
*
455
* Our split-on-spaces logic is lame, so don't expect MOZ_PROFILE_PERF_FLAGS to
456
* work if you pass an argument which includes a space (e.g.
457
* MOZ_PROFILE_PERF_FLAGS="-e 'foo bar'").
458
*/
459
460
# include <signal.h>
461
# include <sys/wait.h>
462
# include <unistd.h>
463
464
static bool perfInitialized = false;
465
static pid_t perfPid = 0;
466
467
bool js_StartPerf() {
468
const char* outfile = "mozperf.data";
469
470
if (perfPid != 0) {
471
UnsafeError("js_StartPerf: called while perf was already running!\n");
472
return false;
473
}
474
475
// Bail if MOZ_PROFILE_WITH_PERF is empty or undefined.
476
if (!getenv("MOZ_PROFILE_WITH_PERF") ||
477
!strlen(getenv("MOZ_PROFILE_WITH_PERF"))) {
478
return true;
479
}
480
481
/*
482
* Delete mozperf.data the first time through -- we're going to append to it
483
* later on, so we want it to be clean when we start out.
484
*/
485
if (!perfInitialized) {
486
perfInitialized = true;
487
unlink(outfile);
488
char cwd[4096];
489
printf("Writing perf profiling data to %s/%s\n", getcwd(cwd, sizeof(cwd)),
490
outfile);
491
}
492
493
pid_t mainPid = getpid();
494
495
pid_t childPid = fork();
496
if (childPid == 0) {
497
/* perf record --pid $mainPID --output=$outfile $MOZ_PROFILE_PERF_FLAGS */
498
499
char mainPidStr[16];
500
SprintfLiteral(mainPidStr, "%d", mainPid);
501
const char* defaultArgs[] = {"perf", "record", "--pid",
502
mainPidStr, "--output", outfile};
503
504
Vector<const char*, 0, SystemAllocPolicy> args;
505
if (!args.append(defaultArgs, ArrayLength(defaultArgs))) {
506
return false;
507
}
508
509
const char* flags = getenv("MOZ_PROFILE_PERF_FLAGS");
510
if (!flags) {
511
flags = "--call-graph";
512
}
513
514
UniqueChars flags2 = DuplicateString(flags);
515
if (!flags2) {
516
return false;
517
}
518
519
// Split |flags2| on spaces.
520
char* toksave;
521
char* tok = strtok_r(flags2.get(), " ", &toksave);
522
while (tok) {
523
if (!args.append(tok)) {
524
return false;
525
}
526
tok = strtok_r(nullptr, " ", &toksave);
527
}
528
529
if (!args.append((char*)nullptr)) {
530
return false;
531
}
532
533
execvp("perf", const_cast<char**>(args.begin()));
534
535
/* Reached only if execlp fails. */
536
fprintf(stderr, "Unable to start perf.\n");
537
exit(1);
538
}
539
if (childPid > 0) {
540
perfPid = childPid;
541
542
/* Give perf a chance to warm up. */
543
usleep(500 * 1000);
544
return true;
545
}
546
UnsafeError("js_StartPerf: fork() failed\n");
547
return false;
548
}
549
550
bool js_StopPerf() {
551
if (perfPid == 0) {
552
UnsafeError("js_StopPerf: perf is not running.\n");
553
return true;
554
}
555
556
if (kill(perfPid, SIGINT)) {
557
UnsafeError("js_StopPerf: kill failed\n");
558
559
// Try to reap the process anyway.
560
waitpid(perfPid, nullptr, WNOHANG);
561
} else {
562
waitpid(perfPid, nullptr, 0);
563
}
564
565
perfPid = 0;
566
return true;
567
}
568
569
#endif /* __linux__ */