1/*
2 * Copyright (C) 2012-2013, 2016 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 "ProfilerDatabase.h"
28
29#include "CatchScope.h"
30#include "CodeBlock.h"
31#include "JSONObject.h"
32#include "ObjectConstructor.h"
33#include "JSCInlines.h"
34#include <wtf/FilePrintStream.h>
35
36namespace JSC { namespace Profiler {
37
38static std::atomic<int> databaseCounter;
39
40static Lock registrationLock;
41static std::atomic<int> didRegisterAtExit;
42static Database* firstDatabase;
43
44Database::Database(VM& vm)
45 : m_databaseID(++databaseCounter)
46 , m_vm(vm)
47 , m_shouldSaveAtExit(false)
48 , m_nextRegisteredDatabase(0)
49{
50}
51
52Database::~Database()
53{
54 if (m_shouldSaveAtExit) {
55 removeDatabaseFromAtExit();
56 performAtExitSave();
57 }
58}
59
60Bytecodes* Database::ensureBytecodesFor(CodeBlock* codeBlock)
61{
62 LockHolder locker(m_lock);
63 return ensureBytecodesFor(locker, codeBlock);
64}
65
66Bytecodes* Database::ensureBytecodesFor(const AbstractLocker&, CodeBlock* codeBlock)
67{
68 codeBlock = codeBlock->baselineAlternative();
69
70 HashMap<CodeBlock*, Bytecodes*>::iterator iter = m_bytecodesMap.find(codeBlock);
71 if (iter != m_bytecodesMap.end())
72 return iter->value;
73
74 m_bytecodes.append(Bytecodes(m_bytecodes.size(), codeBlock));
75 Bytecodes* result = &m_bytecodes.last();
76
77 m_bytecodesMap.add(codeBlock, result);
78
79 return result;
80}
81
82void Database::notifyDestruction(CodeBlock* codeBlock)
83{
84 LockHolder locker(m_lock);
85
86 m_bytecodesMap.remove(codeBlock);
87 m_compilationMap.remove(codeBlock);
88}
89
90void Database::addCompilation(CodeBlock* codeBlock, Ref<Compilation>&& compilation)
91{
92 LockHolder locker(m_lock);
93 ASSERT(!isCompilationThread());
94
95 m_compilations.append(compilation.copyRef());
96 m_compilationMap.set(codeBlock, WTFMove(compilation));
97}
98
99JSValue Database::toJS(JSGlobalObject* globalObject) const
100{
101 VM& vm = globalObject->vm();
102 auto scope = DECLARE_THROW_SCOPE(vm);
103 JSObject* result = constructEmptyObject(globalObject);
104
105 JSArray* bytecodes = constructEmptyArray(globalObject, 0);
106 RETURN_IF_EXCEPTION(scope, { });
107 for (unsigned i = 0; i < m_bytecodes.size(); ++i) {
108 auto value = m_bytecodes[i].toJS(globalObject);
109 RETURN_IF_EXCEPTION(scope, { });
110 bytecodes->putDirectIndex(globalObject, i, value);
111 RETURN_IF_EXCEPTION(scope, { });
112 }
113 result->putDirect(vm, vm.propertyNames->bytecodes, bytecodes);
114
115 JSArray* compilations = constructEmptyArray(globalObject, 0);
116 RETURN_IF_EXCEPTION(scope, { });
117 for (unsigned i = 0; i < m_compilations.size(); ++i) {
118 auto value = m_compilations[i]->toJS(globalObject);
119 RETURN_IF_EXCEPTION(scope, { });
120 compilations->putDirectIndex(globalObject, i, value);
121 RETURN_IF_EXCEPTION(scope, { });
122 }
123 result->putDirect(vm, vm.propertyNames->compilations, compilations);
124
125 JSArray* events = constructEmptyArray(globalObject, 0);
126 RETURN_IF_EXCEPTION(scope, { });
127 for (unsigned i = 0; i < m_events.size(); ++i) {
128 auto value = m_events[i].toJS(globalObject);
129 RETURN_IF_EXCEPTION(scope, { });
130 events->putDirectIndex(globalObject, i, value);
131 RETURN_IF_EXCEPTION(scope, { });
132 }
133 result->putDirect(vm, vm.propertyNames->events, events);
134
135 return result;
136}
137
138String Database::toJSON() const
139{
140 auto scope = DECLARE_THROW_SCOPE(m_vm);
141 JSGlobalObject* globalObject = JSGlobalObject::create(
142 m_vm, JSGlobalObject::createStructure(m_vm, jsNull()));
143
144 auto value = toJS(globalObject);
145 RETURN_IF_EXCEPTION(scope, String());
146 RELEASE_AND_RETURN(scope, JSONStringify(globalObject, value, 0));
147}
148
149bool Database::save(const char* filename) const
150{
151 auto scope = DECLARE_CATCH_SCOPE(m_vm);
152 auto out = FilePrintStream::open(filename, "w");
153 if (!out)
154 return false;
155
156 String data = toJSON();
157 if (UNLIKELY(scope.exception())) {
158 scope.clearException();
159 return false;
160 }
161 out->print(data);
162 return true;
163}
164
165void Database::registerToSaveAtExit(const char* filename)
166{
167 m_atExitSaveFilename = filename;
168
169 if (m_shouldSaveAtExit)
170 return;
171
172 addDatabaseToAtExit();
173 m_shouldSaveAtExit = true;
174}
175
176void Database::logEvent(CodeBlock* codeBlock, const char* summary, const CString& detail)
177{
178 LockHolder locker(m_lock);
179
180 Bytecodes* bytecodes = ensureBytecodesFor(locker, codeBlock);
181 Compilation* compilation = m_compilationMap.get(codeBlock);
182 m_events.append(Event(WallTime::now(), bytecodes, compilation, summary, detail));
183}
184
185void Database::addDatabaseToAtExit()
186{
187 if (++didRegisterAtExit == 1)
188 atexit(atExitCallback);
189
190 LockHolder holder(registrationLock);
191 m_nextRegisteredDatabase = firstDatabase;
192 firstDatabase = this;
193}
194
195void Database::removeDatabaseFromAtExit()
196{
197 LockHolder holder(registrationLock);
198 for (Database** current = &firstDatabase; *current; current = &(*current)->m_nextRegisteredDatabase) {
199 if (*current != this)
200 continue;
201 *current = m_nextRegisteredDatabase;
202 m_nextRegisteredDatabase = 0;
203 m_shouldSaveAtExit = false;
204 break;
205 }
206}
207
208void Database::performAtExitSave() const
209{
210 JSLockHolder lock(m_vm);
211 save(m_atExitSaveFilename.data());
212}
213
214Database* Database::removeFirstAtExitDatabase()
215{
216 LockHolder holder(registrationLock);
217 Database* result = firstDatabase;
218 if (result) {
219 firstDatabase = result->m_nextRegisteredDatabase;
220 result->m_nextRegisteredDatabase = 0;
221 result->m_shouldSaveAtExit = false;
222 }
223 return result;
224}
225
226void Database::atExitCallback()
227{
228 while (Database* database = removeFirstAtExitDatabase())
229 database->performAtExitSave();
230}
231
232} } // namespace JSC::Profiler
233
234