1/*
2 * Copyright (C) 2012-2019 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include <wtf/StackStats.h>
28
29#if ENABLE(STACK_STATS)
30
31#include <wtf/Assertions.h>
32#include <wtf/DataLog.h>
33
34// Define the following flag if you want to collect stats on every single
35// checkpoint. By default, we only log checkpoints that establish new
36// max values.
37
38#define ENABLE_VERBOSE_STACK_STATS 1
39
40
41namespace WTF {
42
43// CheckPoint management:
44Lock StackStats::s_sharedMutex;
45StackStats::CheckPoint* StackStats::s_topCheckPoint = 0;
46StackStats::LayoutCheckPoint* StackStats::s_firstLayoutCheckPoint = 0;
47StackStats::LayoutCheckPoint* StackStats::s_topLayoutCheckPoint = 0;
48
49// High watermark stats:
50int StackStats::s_maxCheckPointDiff = 0;
51int StackStats::s_maxStackHeight = 0;
52int StackStats::s_maxReentryDepth = 0;
53
54int StackStats::s_maxLayoutCheckPointDiff = 0;
55int StackStats::s_maxTotalLayoutCheckPointDiff = 0;
56int StackStats::s_maxLayoutReentryDepth = 0;
57
58
59StackStats::PerThreadStats::PerThreadStats()
60{
61 const StackBounds& stack = Thread::current().stack();
62 m_reentryDepth = 0;
63 m_stackStart = (char*)stack.origin();
64 m_currentCheckPoint = 0;
65
66 dataLogF(" === THREAD new stackStart %p ========\n", m_stackStart);
67}
68
69StackStats::CheckPoint::CheckPoint()
70{
71 std::lock_guard<Lock> lock(StackStats::s_sharedMutex);
72 Thread& thread = Thread::current();
73 StackStats::PerThreadStats& t = thread.stackStats();
74 const StackBounds& stack = thread.stack();
75
76 bool needToLog = false;
77 char* current = reinterpret_cast<char*>(this);
78 char* last = reinterpret_cast<char*>(t.m_currentCheckPoint);
79
80 // If there was no previous checkpoint, measure from the start of the stack:
81 if (!last)
82 last = t.m_stackStart;
83
84 // Update the reentry depth stats:
85 t.m_reentryDepth++;
86 if (t.m_reentryDepth > StackStats::s_maxReentryDepth) {
87 StackStats::s_maxReentryDepth = t.m_reentryDepth;
88 needToLog = true;
89 }
90
91 // Update the stack height stats:
92 int height = t.m_stackStart - current;
93 if (height > StackStats::s_maxStackHeight) {
94 StackStats::s_maxStackHeight = height;
95 needToLog = true;
96 }
97
98 // Update the checkpoint diff stats:
99 int diff = last - current;
100 if (diff > StackStats::s_maxCheckPointDiff) {
101 StackStats::s_maxCheckPointDiff = diff;
102 needToLog = true;
103 }
104
105 // Push this checkpoint:
106 m_prev = t.m_currentCheckPoint;
107 t.m_currentCheckPoint = this;
108
109#if ENABLE(VERBOSE_STACK_STATS)
110 needToLog = true; // always log.
111#endif
112
113 // Log this checkpoint if needed:
114 if (needToLog)
115 dataLogF(" CHECKPOINT %p diff %d/%.1fk/max %.1fk | reentry %d/max %d | height %.1fk/max %.1fk | stack %p size %.1fk\n",
116 this, diff, diff / 1024.0, StackStats::s_maxCheckPointDiff / 1024.0,
117 t.m_reentryDepth, StackStats::s_maxReentryDepth,
118 height / 1024.0, StackStats::s_maxStackHeight / 1024.0,
119 stack.origin(), stack.size() / 1024.0);
120}
121
122StackStats::CheckPoint::~CheckPoint()
123{
124 std::lock_guard<Lock> lock(StackStats::s_sharedMutex);
125 Thread& thread = Thread::current();
126 StackStats::PerThreadStats& t = thread.stackStats();
127
128 // Pop to previous checkpoint:
129 t.m_currentCheckPoint = m_prev;
130 --t.m_reentryDepth;
131
132 // Log this checkpoint if needed:
133#if ENABLE(VERBOSE_STACK_STATS)
134 if (!m_prev) {
135 const StackBounds& stack = thread.stack();
136
137 char* current = reinterpret_cast<char*>(this);
138 int height = t.m_stackStart - current;
139
140 dataLogF(" POP to %p diff max %.1fk | reentry %d/%d max | height %.1fk/max %.1fk | stack %p size %.1fk)\n",
141 this, StackStats::s_maxCheckPointDiff / 1024.0,
142 t.m_reentryDepth, StackStats::s_maxReentryDepth,
143 height / 1024.0, StackStats::s_maxStackHeight / 1024.0,
144 stack.origin(), stack.size() / 1024.0);
145 }
146#endif
147}
148
149void StackStats::probe()
150{
151 std::lock_guard<Lock> lock(StackStats::s_sharedMutex);
152 Thread& thread = Thread::current();
153 StackStats::PerThreadStats& t = thread.stackStats();
154 const StackBounds& stack = thread.stack();
155
156 bool needToLog = false;
157
158 int dummy;
159 char* current = reinterpret_cast<char*>(&dummy);
160 char* last = reinterpret_cast<char*>(t.m_currentCheckPoint);
161
162 // If there was no previous checkpoint, measure from the start of the stack:
163 if (!last)
164 last = t.m_stackStart;
165
166 // We did not reach another checkpoint yet. Hence, we do not touch the
167 // reentry stats.
168
169 // Update the stack height stats:
170 int height = t.m_stackStart - current;
171 if (height > StackStats::s_maxStackHeight) {
172 StackStats::s_maxStackHeight = height;
173 needToLog = true;
174 }
175
176 // Update the checkpoint diff stats:
177 int diff = last - current;
178 if (diff > StackStats::s_maxCheckPointDiff) {
179 StackStats::s_maxCheckPointDiff = diff;
180 needToLog = true;
181 }
182
183#if ENABLE(VERBOSE_STACK_STATS)
184 needToLog = true; // always log.
185#endif
186
187 if (needToLog)
188 dataLogF(" PROBE %p diff %d/%.1fk/max %.1fk | reentry %d/max %d | height %.1fk/max %.1fk | stack %p size %.1fk\n",
189 current, diff, diff / 1024.0, StackStats::s_maxCheckPointDiff / 1024.0,
190 t.m_reentryDepth, StackStats::s_maxReentryDepth,
191 height / 1024.0, StackStats::s_maxStackHeight / 1024.0,
192 stack.origin(), stack.size() / 1024.0);
193}
194
195StackStats::LayoutCheckPoint::LayoutCheckPoint()
196{
197 // While a layout checkpoint is not necessarily a checkpoint where we
198 // we will do a recursion check, it is a convenient spot for doing a
199 // probe to measure the height of stack usage.
200 //
201 // We'll do this probe before we commence with the layout checkpoint.
202 // This is because the probe also locks the sharedLock. By calling the
203 // probe first, we can avoid re-entering the lock.
204 StackStats::probe();
205
206 std::lock_guard<Lock> lock(StackStats::s_sharedMutex);
207 Thread& thread = Thread::current();
208 StackStats::PerThreadStats& t = thread.stackStats();
209 const StackBounds& stack = thread.stack();
210
211 // Push this checkpoint:
212 m_prev = StackStats::s_topLayoutCheckPoint;
213 if (m_prev)
214 m_depth = m_prev->m_depth + 1;
215 else {
216 StackStats::s_firstLayoutCheckPoint = this;
217 m_depth = 0;
218 }
219 StackStats::s_topLayoutCheckPoint = this;
220
221 //
222 char* current = reinterpret_cast<char*>(this);
223 char* last = reinterpret_cast<char*>(m_prev);
224 char* root = reinterpret_cast<char*>(StackStats::s_firstLayoutCheckPoint);
225 bool needToLog = false;
226
227 int diff = last - current;
228 if (!last)
229 diff = 0;
230 int totalDiff = root - current;
231 if (!root)
232 totalDiff = 0;
233
234 // Update the stack height stats:
235 int height = t.m_stackStart - current;
236 if (height > StackStats::s_maxStackHeight) {
237 StackStats::s_maxStackHeight = height;
238 needToLog = true;
239 }
240
241 // Update the layout checkpoint diff stats:
242 if (diff > StackStats::s_maxLayoutCheckPointDiff) {
243 StackStats::s_maxLayoutCheckPointDiff = diff;
244 needToLog = true;
245 }
246
247 // Update the total layout checkpoint diff stats:
248 if (totalDiff > StackStats::s_maxTotalLayoutCheckPointDiff) {
249 StackStats::s_maxTotalLayoutCheckPointDiff = totalDiff;
250 needToLog = true;
251 }
252
253#if ENABLE(VERBOSE_STACK_STATS)
254 needToLog = true; // always log.
255#endif
256
257 if (needToLog)
258 dataLogF(" LAYOUT %p diff %d/%.1fk/max %.1fk | reentry %d/max %d | height %.1fk/max %.1fk | stack %p size %.1fk\n",
259 current, diff, diff / 1024.0, StackStats::s_maxLayoutCheckPointDiff / 1024.0,
260 m_depth, StackStats::s_maxLayoutReentryDepth,
261 totalDiff / 1024.0, StackStats::s_maxTotalLayoutCheckPointDiff / 1024.0,
262 stack.origin(), stack.size() / 1024.0);
263}
264
265StackStats::LayoutCheckPoint::~LayoutCheckPoint()
266{
267 std::lock_guard<Lock> lock(StackStats::s_sharedMutex);
268
269 // Pop to the previous layout checkpoint:
270 StackStats::s_topLayoutCheckPoint = m_prev;
271 if (!m_depth)
272 StackStats::s_firstLayoutCheckPoint = 0;
273}
274
275} // namespace WTF
276
277#endif // ENABLE(STACK_STATS)
278
279