~halega/+junk/sharpdevelop

« back to all changes in this revision

Viewing changes to src/AddIns/Analysis/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs

  • Committer: sk
  • Date: 2011-09-10 05:17:57 UTC
  • Revision ID: halega@halega.com-20110910051757-qfouz1llya9m6boy
4.1.0.7915 Release Candidate 1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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)
 
3
 
 
4
using System;
 
5
using System.Collections.Generic;
 
6
using System.Collections.ObjectModel;
 
7
using System.Data.SQLite;
 
8
using System.Diagnostics;
 
9
using System.Globalization;
 
10
using System.IO;
 
11
using System.Linq;
 
12
 
 
13
using ICSharpCode.Profiler.Interprocess;
 
14
using System.Threading;
 
15
 
 
16
namespace ICSharpCode.Profiler.Controller.Data
 
17
{
 
18
        /// <summary>
 
19
        /// Writes data to a SQLite Database.
 
20
        /// Instance members of this class are not thread-safe.
 
21
        /// </summary>
 
22
        public sealed class ProfilingDataSQLiteWriter : IProfilingDataWriter, IDisposable
 
23
        {
 
24
                SQLiteConnection connection;
 
25
                int dataSetCount = -1;
 
26
                int functionInfoCount;
 
27
                bool isDisposed;
 
28
                int processorFrequency;
 
29
                
 
30
                /// <summary>
 
31
                /// Creates a new SQLite profiling data provider and opens or creates a new database stored in a file.
 
32
                /// </summary>
 
33
                public ProfilingDataSQLiteWriter(string fileName)
 
34
                {
 
35
                        if (File.Exists(fileName))
 
36
                                throw new IOException("File already exists!");
 
37
                        
 
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);
 
47
                        
 
48
                        this.connection.Open();
 
49
                        
 
50
                        InitializeTables();
 
51
                        
 
52
                        File.SetAttributes(fileName, FileAttributes.Compressed);
 
53
                }
 
54
                
 
55
                /// <summary>
 
56
                /// Closes and disposes the database.
 
57
                /// </summary>
 
58
                public void Close()
 
59
                {
 
60
                        if (isDisposed)
 
61
                                return;
 
62
                        
 
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();
 
67
                        }
 
68
                        
 
69
                        this.Dispose();
 
70
                }
 
71
                
 
72
                /// <summary>
 
73
                /// Sets or gets the processor frequency of the computer, where the profiling session was created.
 
74
                /// The processor frequency is measured in MHz.
 
75
                /// </summary>
 
76
                public int ProcessorFrequency {
 
77
                        get {
 
78
                                return this.processorFrequency;
 
79
                        }
 
80
                        set {
 
81
                                processorFrequency = value;
 
82
                                ProfilingDataSQLiteProvider.SetProperty(this.connection.CreateCommand(), "processorfrequency", value.ToString(CultureInfo.InvariantCulture));
 
83
                        }
 
84
                }
 
85
                
 
86
                /// <summary>
 
87
                /// Writes a profiling dataset to the database.
 
88
                /// </summary>
 
89
                public void WriteDataSet(IProfilingDataSet dataSet)
 
90
                {
 
91
                        if (dataSet == null)
 
92
                                throw new ArgumentNullException("dataSet");
 
93
                        
 
94
                        using (SQLiteTransaction transaction = this.connection.BeginTransaction()) {
 
95
                                SQLiteCommand cmd = this.connection.CreateCommand();
 
96
                                
 
97
                                if (dataSetCount == -1)
 
98
                                        dataSetCount = 0;
 
99
                                
 
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));
 
103
                                
 
104
                                cmd.CommandText = "INSERT INTO DataSets(id, isfirst, rootid)" +
 
105
                                        "VALUES(?,?,?);";
 
106
                                
 
107
                                int dataSetStartId = functionInfoCount;
 
108
                                
 
109
                                using (SQLiteCommand loopCommand = this.connection.CreateCommand()) {
 
110
                                        CallTreeNode node = dataSet.RootNode;
 
111
                                        
 
112
                                        loopCommand.CommandText = "INSERT INTO Calls(id, endid, parentid, nameid, cpucyclesspent, cpucyclesspentself, isactiveatstart, callcount)" +
 
113
                                                "VALUES(?,?,?,?,?,?,?,?);";
 
114
                                        
 
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());
 
124
 
 
125
                                        InsertCalls(loopCommand, node, -1, dataParams);
 
126
                                }
 
127
                                
 
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)
 
132
                                                FROM Calls
 
133
                                                WHERE id BETWEEN {1} AND {2}
 
134
                                                GROUP BY nameid;", dataSetCount, dataSetStartId, functionInfoCount - 1);
 
135
                                        
 
136
                                        functionsCommand.ExecuteNonQuery();
 
137
                                }
 
138
                                
 
139
                                cmd.ExecuteNonQuery();
 
140
                                dataSetCount++;
 
141
                                
 
142
                                transaction.Commit();
 
143
                        }
 
144
                }
 
145
                
 
146
                internal const string CallsAndFunctionsTableDefs = @"
 
147
                        CREATE TABLE Calls (
 
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
 
156
                        );
 
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
 
165
                        );
 
166
                        
 
167
                        CREATE TABLE PerformanceCounter(
 
168
                                id INTEGER NOT NULL PRIMARY KEY,
 
169
                                name TEXT NOT NULL,
 
170
                                minvalue REAL NULL,
 
171
                                maxvalue REAL NULL,
 
172
                                format TEXT NOT NULL,
 
173
                                unit TEXT NOT NULL
 
174
                        );
 
175
                        
 
176
                        CREATE TABLE CounterData(
 
177
                                datasetid INTEGER NOT NULL,
 
178
                                counterid INTEGER NOT NULL,
 
179
                                value REAL NOT NULL
 
180
                        );
 
181
                        
 
182
                        CREATE TABLE EventData(
 
183
                                datasetid INTEGER NOT NULL,
 
184
                                eventtype INTEGER NOT NULL,
 
185
                                nameid INTEGER NOT NULL,
 
186
                                data TEXT NULL
 
187
                        );
 
188
";
 
189
                
 
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
 
193
                
 
194
                void InitializeTables()
 
195
                {
 
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 }
 
200
                        //
 
201
                        // NameMapping.Id <-> FunctionData.NameId 1:N
 
202
                        // FunctionData.ParentId <-> FunctionData.Id 1:N
 
203
                        
 
204
                        SQLiteCommand cmd = this.connection.CreateCommand();
 
205
                        
 
206
                        cmd.CommandText = CallsAndFunctionsTableDefs + @"
 
207
                        
 
208
                                CREATE TABLE NameMapping(
 
209
                                        id INTEGER NOT NULL PRIMARY KEY,
 
210
                                        returntype TEXT NOT NULL,
 
211
                                        name TEXT NOT NULL,
 
212
                                        parameters TEXT NOT NULL
 
213
                                );
 
214
                                
 
215
                                /* for CREATE TABLE of Calls and Functions see CallsAndFunctionsTableDefs */
 
216
                                
 
217
                                CREATE TABLE DataSets(
 
218
                                        id INTEGER NOT NULL PRIMARY KEY,
 
219
                                        isfirst INTEGER NOT NULL,
 
220
                                        rootid INTEGER NOT NULL
 
221
                                );
 
222
                                
 
223
                                CREATE TABLE Properties(
 
224
                                        name TEXT NOT NULL PRIMARY KEY,
 
225
                                        value TEXT NOT NULL
 
226
                                );
 
227
                                
 
228
                                INSERT INTO Properties(name, value) VALUES('version', '1.2');
 
229
        ";
 
230
                        
 
231
                        cmd.ExecuteNonQuery();
 
232
                }
 
233
                
 
234
                class CallsParams
 
235
                {
 
236
                        public SQLiteParameter functionInfoId,
 
237
                        parentId, nameId, cpuCyclesSpent, cpuCyclesSpentSelf,
 
238
                        isActiveAtStart, callCount, endId;
 
239
                }
 
240
                
 
241
                void InsertCalls(SQLiteCommand cmd, CallTreeNode node, int parentId, CallsParams dataParams)
 
242
                {
 
243
                        int thisID = functionInfoCount++;
 
244
                        
 
245
                        foreach (CallTreeNode child in node.Children) {
 
246
                                InsertCalls(cmd, child, thisID, dataParams);
 
247
                        }
 
248
                        
 
249
                        long cpuCycles = node.CpuCyclesSpent;
 
250
                        long cpuCyclesSelf = node.CpuCyclesSpentSelf;
 
251
                        
 
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");
 
255
                        }
 
256
                        
 
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");
 
259
                        }
 
260
                        
 
261
                        dataParams.callCount.Value = node.RawCallCount;
 
262
                        dataParams.isActiveAtStart.Value = node.IsActiveAtStart;
 
263
                        dataParams.cpuCyclesSpent.Value = cpuCycles;
 
264
                        dataParams.cpuCyclesSpentSelf.Value = cpuCyclesSelf;
 
265
 
 
266
                        dataParams.functionInfoId.Value = thisID;
 
267
                        dataParams.nameId.Value = node.NameMapping.Id;
 
268
                        dataParams.parentId.Value = parentId;
 
269
                        dataParams.endId.Value = functionInfoCount - 1;
 
270
                        
 
271
                        cmd.ExecuteNonQuery();
 
272
                }
 
273
                
 
274
                /// <summary>
 
275
                /// Writes a name mapping to the database.
 
276
                /// </summary>
 
277
                public void WriteMappings(IEnumerable<NameMapping> mappings)
 
278
                {
 
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");
 
285
                                        
 
286
                                        cmd.CommandText = "INSERT INTO NameMapping(id, returntype, name, parameters)" +
 
287
                                                "VALUES(?,?,?,?);";
 
288
                                        
 
289
                                        cmd.Parameters.AddRange(new SQLiteParameter[] { idParam, retTParam, nameParam, paramsParam });
 
290
                                        
 
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()) : "");
 
296
                                                
 
297
                                                cmd.ExecuteNonQuery();
 
298
                                        }
 
299
                                }
 
300
                                trans.Commit();
 
301
                        }
 
302
                }
 
303
                
 
304
                /// <summary>
 
305
                /// Closes the connection to the database.
 
306
                /// </summary>
 
307
                public void Dispose()
 
308
                {
 
309
                        if (!isDisposed)
 
310
                                this.connection.Close();
 
311
                        
 
312
                        isDisposed = true;
 
313
                }
 
314
                
 
315
                /// <inheritdoc/>
 
316
                public void WritePerformanceCounterData(IEnumerable<PerformanceCounterDescriptor> counters)
 
317
                {
 
318
                        using (SQLiteTransaction trans = this.connection.BeginTransaction()) {
 
319
                                using (SQLiteCommand cmd = this.connection.CreateCommand()) {
 
320
                                        using (SQLiteCommand cmd2 = this.connection.CreateCommand()) {
 
321
 
 
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");
 
330
                                                
 
331
                                                cmd.Parameters.AddRange(new SQLiteParameter[] { idParam, dataSetParam, valueParam });
 
332
                                                cmd2.Parameters.AddRange(new SQLiteParameter[] { idParam, nameParam, minParam, maxParam, unitParam, formatParam });
 
333
                                                
 
334
                                                cmd2.CommandText =
 
335
                                                        "INSERT INTO PerformanceCounter(id, name, minvalue, maxvalue, unit, format)" +
 
336
                                                        "VALUES(@id,@name,@min,@max,@unit,@format);";
 
337
                                                
 
338
                                                cmd.CommandText =
 
339
                                                        "INSERT INTO CounterData(datasetid, counterid, value)" +
 
340
                                                        "VALUES(@dataset,@id,@value);";
 
341
                                                
 
342
                                                int id = 0;
 
343
                                                
 
344
                                                foreach (PerformanceCounterDescriptor counter in counters) {
 
345
                                                        idParam.Value = id;
 
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;
 
351
                                                        
 
352
                                                        for (int i = 0; i < counter.Values.Count; i++) {
 
353
                                                                dataSetParam.Value = i;
 
354
                                                                valueParam.Value = counter.Values[i];
 
355
                                                                cmd.ExecuteNonQuery();
 
356
                                                        }
 
357
                                                        
 
358
                                                        cmd2.ExecuteNonQuery();
 
359
                                                        
 
360
                                                        id++;
 
361
                                                }
 
362
                                        }
 
363
                                }
 
364
                                trans.Commit();
 
365
                        }
 
366
                }
 
367
                
 
368
                /// <inheritdoc/>
 
369
                public void WriteEventData(IEnumerable<EventDataEntry> events)
 
370
                {
 
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");
 
377
                                        
 
378
                                        cmd.CommandText =
 
379
                                                "INSERT INTO EventData(datasetid,eventtype,nameid,data) " +
 
380
                                                "VALUES(@datasetid,@eventtype,@nameid,@data);";
 
381
                                        
 
382
                                        cmd.Parameters.AddRange(new SQLiteParameter[] { dataSetParam, eventTypeParam, nameIdParam, dataParam });
 
383
                                        
 
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();
 
390
                                        }
 
391
                                }
 
392
                                trans.Commit();
 
393
                        }
 
394
                }
 
395
                
 
396
                /// <inheritdoc/>
 
397
                public int DataSetCount {
 
398
                        get { return this.dataSetCount; }
 
399
                }
 
400
        }
 
401
}