Line data Source code
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 : #include "KeyedStackCapturer.h"
8 : #include "nsPrintfCString.h"
9 : #include "mozilla/StackWalk.h"
10 : #include "ProcessedStack.h"
11 : #include "jsapi.h"
12 :
13 : namespace {
14 :
15 : /** Defines the size of the keyed stack dictionary. */
16 : const uint8_t kMaxKeyLength = 50;
17 :
18 : /** The maximum number of captured stacks that we're keeping. */
19 : const size_t kMaxCapturedStacksKept = 50;
20 :
21 : /**
22 : * Checks if a single character of the key string is valid.
23 : *
24 : * @param aChar a character to validate.
25 : * @return True, if the char is valid, False - otherwise.
26 : */
27 : bool
28 0 : IsKeyCharValid(const char aChar)
29 : {
30 0 : return (aChar >= 'A' && aChar <= 'Z')
31 0 : || (aChar >= 'a' && aChar <= 'z')
32 0 : || (aChar >= '0' && aChar <= '9')
33 0 : || aChar == '-';
34 : }
35 :
36 : /**
37 : * Checks if a given string is a valid telemetry key.
38 : *
39 : * @param aKey is the key string.
40 : * @return True, if the key is valid, False - otherwise.
41 : */
42 : bool
43 0 : IsKeyValid(const nsACString& aKey)
44 : {
45 : // Check key length.
46 0 : if (aKey.Length() > kMaxKeyLength) {
47 0 : return false;
48 : }
49 :
50 : // Check key characters.
51 0 : const char* cur = aKey.BeginReading();
52 0 : const char* end = aKey.EndReading();
53 :
54 0 : for (; cur < end; ++cur) {
55 0 : if (!IsKeyCharValid(*cur)) {
56 0 : return false;
57 : }
58 : }
59 0 : return true;
60 : }
61 :
62 : } // anonymous namespace
63 :
64 : namespace mozilla {
65 : namespace Telemetry {
66 :
67 0 : void KeyedStackCapturer::Capture(const nsACString& aKey) {
68 0 : MutexAutoLock captureStackMutex(mStackCapturerMutex);
69 :
70 : // Check if the key is ok.
71 0 : if (!IsKeyValid(aKey)) {
72 0 : NS_WARNING(nsPrintfCString(
73 : "Invalid key is used to capture stack in telemetry: '%s'",
74 : PromiseFlatCString(aKey).get()
75 0 : ).get());
76 0 : return;
77 : }
78 :
79 : // Trying to find and update the stack information.
80 0 : StackFrequencyInfo* info = mStackInfos.Get(aKey);
81 0 : if (info) {
82 : // We already recorded this stack before, only increase the count.
83 0 : info->mCount++;
84 0 : return;
85 : }
86 :
87 : // Check if we have room for new captures.
88 0 : if (mStackInfos.Count() >= kMaxCapturedStacksKept) {
89 : // Addressed by Bug 1316793.
90 0 : return;
91 : }
92 :
93 : // We haven't captured a stack for this key before, do it now.
94 : // Note that this does a stackwalk and is an expensive operation.
95 0 : std::vector<uintptr_t> rawStack;
96 0 : auto callback = [](uint32_t, void* aPC, void*, void* aClosure) {
97 : std::vector<uintptr_t>* stack =
98 0 : static_cast<std::vector<uintptr_t>*>(aClosure);
99 0 : stack->push_back(reinterpret_cast<uintptr_t>(aPC));
100 0 : };
101 0 : MozStackWalk(callback, /* skipFrames */ 0,
102 0 : /* maxFrames */ 0, reinterpret_cast<void*>(&rawStack), 0, nullptr);
103 0 : ProcessedStack stack = GetStackAndModules(rawStack);
104 :
105 : // Store the new stack info.
106 0 : size_t stackIndex = mStacks.AddStack(stack);
107 0 : mStackInfos.Put(aKey, new StackFrequencyInfo(1, stackIndex));
108 : }
109 :
110 : NS_IMETHODIMP
111 0 : KeyedStackCapturer::ReflectCapturedStacks(JSContext *cx, JS::MutableHandle<JS::Value> ret)
112 : {
113 0 : MutexAutoLock capturedStackMutex(mStackCapturerMutex);
114 :
115 : // this adds the memoryMap and stacks properties.
116 0 : JS::RootedObject fullReportObj(cx, CreateJSStackObject(cx, mStacks));
117 0 : if (!fullReportObj) {
118 0 : return NS_ERROR_FAILURE;
119 : }
120 :
121 0 : JS::RootedObject keysArray(cx, JS_NewArrayObject(cx, 0));
122 0 : if (!keysArray) {
123 0 : return NS_ERROR_FAILURE;
124 : }
125 :
126 0 : bool ok = JS_DefineProperty(cx, fullReportObj, "captures",
127 0 : keysArray, JSPROP_ENUMERATE);
128 0 : if (!ok) {
129 0 : return NS_ERROR_FAILURE;
130 : }
131 :
132 0 : size_t keyIndex = 0;
133 0 : for (auto iter = mStackInfos.ConstIter(); !iter.Done(); iter.Next(), ++keyIndex) {
134 0 : const StackFrequencyInfo* info = iter.Data();
135 :
136 0 : JS::RootedObject infoArray(cx, JS_NewArrayObject(cx, 0));
137 0 : if (!keysArray) {
138 0 : return NS_ERROR_FAILURE;
139 : }
140 0 : JS::RootedString str(cx, JS_NewStringCopyZ(cx,
141 0 : PromiseFlatCString(iter.Key()).get()));
142 0 : if (!str ||
143 0 : !JS_DefineElement(cx, infoArray, 0, str, JSPROP_ENUMERATE) ||
144 0 : !JS_DefineElement(cx, infoArray, 1, info->mIndex, JSPROP_ENUMERATE) ||
145 0 : !JS_DefineElement(cx, infoArray, 2, info->mCount, JSPROP_ENUMERATE) ||
146 0 : !JS_DefineElement(cx, keysArray, keyIndex, infoArray, JSPROP_ENUMERATE)) {
147 0 : return NS_ERROR_FAILURE;
148 : }
149 : }
150 :
151 0 : ret.setObject(*fullReportObj);
152 0 : return NS_OK;
153 : }
154 :
155 : void
156 0 : KeyedStackCapturer::Clear()
157 : {
158 0 : MutexAutoLock captureStackMutex(mStackCapturerMutex);
159 0 : mStackInfos.Clear();
160 0 : mStacks.Clear();
161 0 : }
162 :
163 : } // namespace Telemetry
164 : } // namespace mozilla
|