1
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
2
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
5
using System.Collections.Generic;
6
using System.Collections.ObjectModel;
7
using System.Data.SQLite;
8
using System.Diagnostics;
9
using System.Globalization;
13
using ICSharpCode.Profiler.Interprocess;
14
using System.Threading;
16
namespace ICSharpCode.Profiler.Controller.Data
19
/// Writes data to a SQLite Database.
20
/// Instance members of this class are not thread-safe.
22
public sealed class ProfilingDataSQLiteWriter : IProfilingDataWriter, IDisposable
24
SQLiteConnection connection;
25
int dataSetCount = -1;
26
int functionInfoCount;
28
int processorFrequency;
31
/// Creates a new SQLite profiling data provider and opens or creates a new database stored in a file.
33
public ProfilingDataSQLiteWriter(string fileName)
35
if (File.Exists(fileName))
36
throw new IOException("File already exists!");
38
SQLiteConnectionStringBuilder conn = new SQLiteConnectionStringBuilder();
39
conn.Add("Data Source", fileName);
40
conn.Add("New", true);
41
// Disable protecting the database on crashes - it's a new database,
42
// it may go corrupt if we crash during DB creation. Disabling journalling
43
// makes Inserts faster.
44
conn.Add("Journal Mode", "OFF");
45
conn.Add("Synchronous", "OFF");
46
this.connection = new SQLiteConnection(conn.ConnectionString);
48
this.connection.Open();
52
File.SetAttributes(fileName, FileAttributes.Compressed);
56
/// Closes and disposes the database.
63
using (SQLiteCommand cmd = this.connection.CreateCommand()) {
64
// create index at the end (after inserting data), this is faster
65
cmd.CommandText = CallsAndFunctionsIndexDefs;
66
cmd.ExecuteNonQuery();
73
/// Sets or gets the processor frequency of the computer, where the profiling session was created.
74
/// The processor frequency is measured in MHz.
76
public int ProcessorFrequency {
78
return this.processorFrequency;
81
processorFrequency = value;
82
ProfilingDataSQLiteProvider.SetProperty(this.connection.CreateCommand(), "processorfrequency", value.ToString(CultureInfo.InvariantCulture));
87
/// Writes a profiling dataset to the database.
89
public void WriteDataSet(IProfilingDataSet dataSet)
92
throw new ArgumentNullException("dataSet");
94
using (SQLiteTransaction transaction = this.connection.BeginTransaction()) {
95
SQLiteCommand cmd = this.connection.CreateCommand();
97
if (dataSetCount == -1)
100
cmd.Parameters.Add(new SQLiteParameter("id", dataSetCount));
101
cmd.Parameters.Add(new SQLiteParameter("isfirst", dataSet.IsFirst));
102
cmd.Parameters.Add(new SQLiteParameter("rootid", functionInfoCount));
104
cmd.CommandText = "INSERT INTO DataSets(id, isfirst, rootid)" +
107
int dataSetStartId = functionInfoCount;
109
using (SQLiteCommand loopCommand = this.connection.CreateCommand()) {
110
CallTreeNode node = dataSet.RootNode;
112
loopCommand.CommandText = "INSERT INTO Calls(id, endid, parentid, nameid, cpucyclesspent, cpucyclesspentself, isactiveatstart, callcount)" +
113
"VALUES(?,?,?,?,?,?,?,?);";
115
CallsParams dataParams = new CallsParams();
116
loopCommand.Parameters.Add(dataParams.functionInfoId = new SQLiteParameter());
117
loopCommand.Parameters.Add(dataParams.endId = new SQLiteParameter());
118
loopCommand.Parameters.Add(dataParams.parentId = new SQLiteParameter());
119
loopCommand.Parameters.Add(dataParams.nameId = new SQLiteParameter());
120
loopCommand.Parameters.Add(dataParams.cpuCyclesSpent = new SQLiteParameter());
121
loopCommand.Parameters.Add(dataParams.cpuCyclesSpentSelf = new SQLiteParameter());
122
loopCommand.Parameters.Add(dataParams.isActiveAtStart = new SQLiteParameter());
123
loopCommand.Parameters.Add(dataParams.callCount = new SQLiteParameter());
125
InsertCalls(loopCommand, node, -1, dataParams);
128
using (SQLiteCommand functionsCommand = this.connection.CreateCommand()) {
129
functionsCommand.CommandText = string.Format(@"
130
INSERT INTO Functions
131
SELECT {0}, nameid, SUM(cpucyclesspent), SUM(cpucyclesspentself), SUM(isactiveatstart), SUM(callcount), MAX(id != endid)
133
WHERE id BETWEEN {1} AND {2}
134
GROUP BY nameid;", dataSetCount, dataSetStartId, functionInfoCount - 1);
136
functionsCommand.ExecuteNonQuery();
139
cmd.ExecuteNonQuery();
142
transaction.Commit();
146
internal const string CallsAndFunctionsTableDefs = @"
148
id INTEGER NOT NULL PRIMARY KEY,
149
endid INTEGER NOT NULL,
150
parentid INTEGER NOT NULL,
151
nameid INTEGER NOT NULL,
152
cpucyclesspent INTEGER NOT NULL,
153
cpucyclesspentself INTEGER NOT NULL,
154
isactiveatstart INTEGER NOT NULL,
155
callcount INTEGER NOT NULL
157
CREATE TABLE Functions (
158
datasetid INTEGER NOT NULL,
159
nameid INTEGER NOT NULL,
160
cpucyclesspent INTEGER NOT NULL,
161
cpucyclesspentself INTEGER NOT NULL,
162
activecallcount INTEGER NOT NULL,
163
callcount INTEGER NOT NULL,
164
hasChildren INTEGER NOT NULL
167
CREATE TABLE PerformanceCounter(
168
id INTEGER NOT NULL PRIMARY KEY,
172
format TEXT NOT NULL,
176
CREATE TABLE CounterData(
177
datasetid INTEGER NOT NULL,
178
counterid INTEGER NOT NULL,
182
CREATE TABLE EventData(
183
datasetid INTEGER NOT NULL,
184
eventtype INTEGER NOT NULL,
185
nameid INTEGER NOT NULL,
190
internal const string CallsAndFunctionsIndexDefs =
191
"CREATE INDEX CallsParent ON Calls(parentid ASC);" // required for searching the children
192
+ " ANALYZE;"; // make SQLite analyze the indices available; this will help the query planner later
194
void InitializeTables()
196
// NameMapping { Id, ReturnType, Name, Parameters }
197
// Calls { id, endid, parentid, nameid, cpucyclesspent, cpucyclesspentself, isactiveatstart, callcount }
198
// Functions { datasetid, nameid, cpucyclesspent, cpucyclesspentself, activecallcount, callcount, haschildren }
199
// DataSets { Id, IsFirst, RootId }
201
// NameMapping.Id <-> FunctionData.NameId 1:N
202
// FunctionData.ParentId <-> FunctionData.Id 1:N
204
SQLiteCommand cmd = this.connection.CreateCommand();
206
cmd.CommandText = CallsAndFunctionsTableDefs + @"
208
CREATE TABLE NameMapping(
209
id INTEGER NOT NULL PRIMARY KEY,
210
returntype TEXT NOT NULL,
212
parameters TEXT NOT NULL
215
/* for CREATE TABLE of Calls and Functions see CallsAndFunctionsTableDefs */
217
CREATE TABLE DataSets(
218
id INTEGER NOT NULL PRIMARY KEY,
219
isfirst INTEGER NOT NULL,
220
rootid INTEGER NOT NULL
223
CREATE TABLE Properties(
224
name TEXT NOT NULL PRIMARY KEY,
228
INSERT INTO Properties(name, value) VALUES('version', '1.2');
231
cmd.ExecuteNonQuery();
236
public SQLiteParameter functionInfoId,
237
parentId, nameId, cpuCyclesSpent, cpuCyclesSpentSelf,
238
isActiveAtStart, callCount, endId;
241
void InsertCalls(SQLiteCommand cmd, CallTreeNode node, int parentId, CallsParams dataParams)
243
int thisID = functionInfoCount++;
245
foreach (CallTreeNode child in node.Children) {
246
InsertCalls(cmd, child, thisID, dataParams);
249
long cpuCycles = node.CpuCyclesSpent;
250
long cpuCyclesSelf = node.CpuCyclesSpentSelf;
252
// we sometimes saw invalid data with the 0x0080000000000000L bit set
253
if (cpuCycles > 0x0007ffffffffffffL || cpuCycles < 0) {
254
throw new InvalidOperationException("Too large CpuCyclesSpent - there's something wrong in the data");
257
if (node.NameMapping.Id != 0 && (cpuCyclesSelf > cpuCycles || cpuCyclesSelf < 0)) {
258
throw new InvalidOperationException("Too large/small CpuCyclesSpentSelf (" + cpuCyclesSelf + ") - there's something wrong in the data");
261
dataParams.callCount.Value = node.RawCallCount;
262
dataParams.isActiveAtStart.Value = node.IsActiveAtStart;
263
dataParams.cpuCyclesSpent.Value = cpuCycles;
264
dataParams.cpuCyclesSpentSelf.Value = cpuCyclesSelf;
266
dataParams.functionInfoId.Value = thisID;
267
dataParams.nameId.Value = node.NameMapping.Id;
268
dataParams.parentId.Value = parentId;
269
dataParams.endId.Value = functionInfoCount - 1;
271
cmd.ExecuteNonQuery();
275
/// Writes a name mapping to the database.
277
public void WriteMappings(IEnumerable<NameMapping> mappings)
279
using (SQLiteTransaction trans = this.connection.BeginTransaction()) {
280
using (SQLiteCommand cmd = this.connection.CreateCommand()) {
281
SQLiteParameter idParam = new SQLiteParameter("id");
282
SQLiteParameter retTParam = new SQLiteParameter("returntype");
283
SQLiteParameter nameParam = new SQLiteParameter("name");
284
SQLiteParameter paramsParam = new SQLiteParameter("parameters");
286
cmd.CommandText = "INSERT INTO NameMapping(id, returntype, name, parameters)" +
289
cmd.Parameters.AddRange(new SQLiteParameter[] { idParam, retTParam, nameParam, paramsParam });
291
foreach (NameMapping mapping in mappings) {
292
idParam.Value = mapping.Id;
293
retTParam.Value = mapping.ReturnType;
294
nameParam.Value = mapping.Name;
295
paramsParam.Value = ((mapping.Parameters != null) ? string.Join("-", mapping.Parameters.ToArray()) : "");
297
cmd.ExecuteNonQuery();
305
/// Closes the connection to the database.
307
public void Dispose()
310
this.connection.Close();
316
public void WritePerformanceCounterData(IEnumerable<PerformanceCounterDescriptor> counters)
318
using (SQLiteTransaction trans = this.connection.BeginTransaction()) {
319
using (SQLiteCommand cmd = this.connection.CreateCommand()) {
320
using (SQLiteCommand cmd2 = this.connection.CreateCommand()) {
322
SQLiteParameter idParam = new SQLiteParameter("id");
323
SQLiteParameter nameParam = new SQLiteParameter("name");
324
SQLiteParameter dataSetParam = new SQLiteParameter("dataset");
325
SQLiteParameter valueParam = new SQLiteParameter("value");
326
SQLiteParameter minParam = new SQLiteParameter("min");
327
SQLiteParameter maxParam = new SQLiteParameter("max");
328
SQLiteParameter unitParam = new SQLiteParameter("unit");
329
SQLiteParameter formatParam = new SQLiteParameter("format");
331
cmd.Parameters.AddRange(new SQLiteParameter[] { idParam, dataSetParam, valueParam });
332
cmd2.Parameters.AddRange(new SQLiteParameter[] { idParam, nameParam, minParam, maxParam, unitParam, formatParam });
335
"INSERT INTO PerformanceCounter(id, name, minvalue, maxvalue, unit, format)" +
336
"VALUES(@id,@name,@min,@max,@unit,@format);";
339
"INSERT INTO CounterData(datasetid, counterid, value)" +
340
"VALUES(@dataset,@id,@value);";
344
foreach (PerformanceCounterDescriptor counter in counters) {
346
nameParam.Value = counter.Name;
347
minParam.Value = counter.MinValue;
348
maxParam.Value = counter.MaxValue;
349
unitParam.Value = counter.Unit;
350
formatParam.Value = counter.Format;
352
for (int i = 0; i < counter.Values.Count; i++) {
353
dataSetParam.Value = i;
354
valueParam.Value = counter.Values[i];
355
cmd.ExecuteNonQuery();
358
cmd2.ExecuteNonQuery();
369
public void WriteEventData(IEnumerable<EventDataEntry> events)
371
using (SQLiteTransaction trans = this.connection.BeginTransaction()) {
372
using (SQLiteCommand cmd = this.connection.CreateCommand()) {
373
SQLiteParameter dataSetParam = new SQLiteParameter("datasetid");
374
SQLiteParameter eventTypeParam = new SQLiteParameter("eventtype");
375
SQLiteParameter nameIdParam = new SQLiteParameter("nameid");
376
SQLiteParameter dataParam = new SQLiteParameter("data");
379
"INSERT INTO EventData(datasetid,eventtype,nameid,data) " +
380
"VALUES(@datasetid,@eventtype,@nameid,@data);";
382
cmd.Parameters.AddRange(new SQLiteParameter[] { dataSetParam, eventTypeParam, nameIdParam, dataParam });
384
foreach (EventDataEntry entry in events) {
385
dataSetParam.Value = entry.DataSetId;
386
eventTypeParam.Value = (int)entry.Type;
387
nameIdParam.Value = entry.NameId;
388
dataParam.Value = entry.Data;
389
cmd.ExecuteNonQuery();
397
public int DataSetCount {
398
get { return this.dataSetCount; }