#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

317 lines
9.9 KiB

// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Siegfried Pammer" email="sie_pam@gmx.at"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.SQLite;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using ICSharpCode.Profiler.Interprocess;
using System.Threading;
namespace ICSharpCode.Profiler.Controller.Data
{
/// <summary>
/// Writes data to a SQLite Database.
/// Instance members of this class are not thread-safe.
/// </summary>
public sealed class ProfilingDataSQLiteWriter : IProfilingDataWriter, IDisposable
{
SQLiteConnection connection;
int dataSetCount = -1;
int functionInfoCount;
bool isDisposed;
int processorFrequency;
bool profileUnitTests;
string[] unitTestNames;
/// <summary>
/// Creates a new SQLite profiling data provider and opens or creates a new database stored in a file.
/// </summary>
public ProfilingDataSQLiteWriter(string fileName, bool profileUnitTests, string[] unitTestNames)
{
if (File.Exists(fileName))
throw new IOException("File already exists!");
SQLiteConnectionStringBuilder conn = new SQLiteConnectionStringBuilder();
conn.Add("Data Source", fileName);
conn.Add("New", true);
// Disable protecting the database on crashes - it's a new database,
// it may go corrupt if we crash during DB creation. Disabling journalling
// makes Inserts faster.
conn.Add("Journal Mode", "OFF");
conn.Add("Synchronous", "OFF");
this.connection = new SQLiteConnection(conn.ConnectionString);
this.connection.Open();
InitializeTables();
File.SetAttributes(fileName, FileAttributes.Compressed);
this.profileUnitTests = profileUnitTests;
this.unitTestNames = unitTestNames;
if (profileUnitTests && unitTestNames == null)
throw new InvalidOperationException("Please add unit tests to filter!");
}
/// <summary>
/// Closes and disposes the database.
/// </summary>
public void Close()
{
using (SQLiteCommand cmd = this.connection.CreateCommand()) {
// create index at the end (after inserting data), this is faster
cmd.CommandText = @"CREATE INDEX Parents ON FunctionData(parentid ASC);";
cmd.ExecuteNonQuery();
// make SQLite analyze the indices available; this will help the query planner later
cmd.CommandText = @"ANALYZE;";
cmd.ExecuteNonQuery();
}
this.Dispose();
}
/// <summary>
/// Sets or gets the processor frequency of the computer, where the profiling session was created.
/// The processor frequency is measured in MHz.
/// </summary>
public int ProcessorFrequency {
get {
return this.processorFrequency;
}
set {
processorFrequency = value;
ProfilingDataSQLiteProvider.SetProperty(this.connection.CreateCommand(), "processorfrequency", value.ToString(CultureInfo.InvariantCulture));
}
}
/// <summary>
/// Writes a profiling dataset to the database.
/// </summary>
public void WriteDataSet(IProfilingDataSet dataSet)
{
if (dataSet == null)
throw new ArgumentNullException("dataSet");
using (SQLiteTransaction transaction = this.connection.BeginTransaction()) {
SQLiteCommand cmd = this.connection.CreateCommand();
if (dataSetCount == -1)
dataSetCount = 0;
cmd.Parameters.Add(new SQLiteParameter("id", dataSetCount));
cmd.Parameters.Add(new SQLiteParameter("cpuuage", dataSet.CpuUsage.ToString(CultureInfo.InvariantCulture)));
cmd.Parameters.Add(new SQLiteParameter("isfirst", dataSet.IsFirst));
cmd.Parameters.Add(new SQLiteParameter("rootid", functionInfoCount));
cmd.CommandText = "INSERT INTO DataSets(id, cpuusage, isfirst, rootid)" +
"VALUES(?,?,?,?);";
using (SQLiteCommand loopCommand = this.connection.CreateCommand()) {
CallTreeNode node = dataSet.RootNode;
loopCommand.CommandText = "INSERT INTO FunctionData(datasetid, id, endid, parentid, nameid, timespent, isactiveatstart, callcount)" +
"VALUES(?,?,?,?,?,?,?,?);";
FunctionDataParams dataParams = new FunctionDataParams();
loopCommand.Parameters.Add(dataParams.dataSetId = new SQLiteParameter());
loopCommand.Parameters.Add(dataParams.functionInfoId = new SQLiteParameter());
loopCommand.Parameters.Add(dataParams.endId = new SQLiteParameter());
loopCommand.Parameters.Add(dataParams.parentId = new SQLiteParameter());
loopCommand.Parameters.Add(dataParams.nameId = new SQLiteParameter());
loopCommand.Parameters.Add(dataParams.cpuCyclesSpent = new SQLiteParameter());
loopCommand.Parameters.Add(dataParams.isActiveAtStart = new SQLiteParameter());
loopCommand.Parameters.Add(dataParams.callCount = new SQLiteParameter());
bool addedData = true;
if (profileUnitTests)
addedData = FindUnitTestsAndInsert(loopCommand, node, dataSet, dataParams);
else
InsertTree(loopCommand, node, -1, dataSet, dataParams);
if (addedData) {
cmd.ExecuteNonQuery();
dataSetCount++;
}
}
transaction.Commit();
}
}
bool IsUnitTest(NameMapping name)
{
return unitTestNames.Contains(name.Name);
}
bool FindUnitTestsAndInsert(SQLiteCommand cmd, CallTreeNode node, IProfilingDataSet dataSet, FunctionDataParams dataParams)
{
List<CallTreeNode> list = new List<CallTreeNode>();
FindUnitTests(node, list);
if (list.Count > 0) {
InsertTree(cmd, new UnitTestRootCallTreeNode(list), -1, dataSet, dataParams);
return true;
}
return false;
}
void FindUnitTests(CallTreeNode parentNode, IList<CallTreeNode> list)
{
if (IsUnitTest(parentNode.NameMapping)) {
list.Add(parentNode);
return;
}
foreach (var node in parentNode.Children) {
FindUnitTests(node, list);
}
}
void InitializeTables()
{
//NameMapping { Id, ReturnType, Name, Parameters }
// TODO : update db schema: change FunctionData.TimeSpent to cpucyclesspent
//FunctionData { DataSetId, Id, ParentId, NameId, TimeSpent, CallCount }
//DataSets { Id, CPUUsage, RootId }
//
//NameMapping.Id <-> FunctionData.NameId 1:N
//FunctionData.ParentId <-> FunctionData.Id 1:N
SQLiteCommand cmd = this.connection.CreateCommand();
cmd.CommandText = @"
CREATE TABLE NameMapping(
id INTEGER NOT NULL PRIMARY KEY,
returntype VARCHAR2(100) NOT NULL,
name VARCHAR2(255) NOT NULL,
parameters VARCHAR2(1000) NOT NULL
);
CREATE TABLE FunctionData(
datasetid INTEGER NOT NULL,
id INTEGER NOT NULL PRIMARY KEY,
endid INTEGER NOT NULL,
parentid INTEGER NOT NULL,
nameid INTEGER NOT NULL,
timespent INT8 NOT NULL,
isactiveatstart INTEGER NOT NULL,
callcount INTEGER NOT NULL
);
CREATE TABLE DataSets(
id INTEGER NOT NULL PRIMARY KEY,
cpuusage REAL NOT NULL,
isfirst INTEGER NOT NULL,
rootid INTEGER NOT NULL
);
CREATE TABLE Properties(
name VARCHAR2(100) NOT NULL PRIMARY KEY,
value VARCHAR2(100) NOT NULL
);
INSERT INTO Properties(name, value) VALUES('version', '1.0');
CREATE TABLE PerformanceCounter(
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR2(100) NOT NULL
);
CREATE TABLE CounterData(
datasetid INTEGER NOT NULL,
counterid INTEGER NOT NULL,
value INTEGER NOT NULL
);
";
cmd.ExecuteNonQuery();
}
class FunctionDataParams
{
public SQLiteParameter dataSetId, functionInfoId,
parentId, nameId, cpuCyclesSpent, isActiveAtStart, callCount, endId;
}
void InsertTree(SQLiteCommand cmd, CallTreeNode node, int parentId, IProfilingDataSet dataSet, FunctionDataParams dataParams)
{
int thisID = functionInfoCount++;
foreach (CallTreeNode child in node.Children) {
InsertTree(cmd, child, thisID, dataSet, dataParams);
}
// we sometimes saw invalid data with the 0x0080000000000000L bit set
if (node.CpuCyclesSpent > 0x0007ffffffffffffL || node.CpuCyclesSpent < 0) {
throw new InvalidOperationException("Too large CpuCyclesSpent - there's something wrong in the data");
}
dataParams.callCount.Value = node.RawCallCount;
dataParams.isActiveAtStart.Value = node.IsActiveAtStart;
dataParams.cpuCyclesSpent.Value = node.CpuCyclesSpent;
dataParams.dataSetId.Value = dataSetCount;
dataParams.functionInfoId.Value = thisID;
dataParams.nameId.Value = node.NameMapping.Id;
dataParams.parentId.Value = parentId;
dataParams.endId.Value = functionInfoCount - 1;
cmd.ExecuteNonQuery();
}
/// <summary>
/// Writes a name mapping to the database.
/// </summary>
public void WriteMappings(IEnumerable<NameMapping> mappings)
{
using (SQLiteTransaction trans = this.connection.BeginTransaction()) {
using (SQLiteCommand cmd = this.connection.CreateCommand()) {
SQLiteParameter idParam = new SQLiteParameter("id");
SQLiteParameter retTParam = new SQLiteParameter("returntype");
SQLiteParameter nameParam = new SQLiteParameter("name");
SQLiteParameter paramsParam = new SQLiteParameter("parameters");
cmd.CommandText = "INSERT INTO NameMapping(id, returntype, name, parameters)" +
"VALUES(?,?,?,?);";
cmd.Parameters.AddRange(new SQLiteParameter[] { idParam, retTParam, nameParam, paramsParam });
foreach (NameMapping mapping in mappings) {
idParam.Value = mapping.Id;
retTParam.Value = mapping.ReturnType;
nameParam.Value = mapping.Name;
paramsParam.Value = ((mapping.Parameters != null) ? string.Join("-", mapping.Parameters.ToArray()) : "");
cmd.ExecuteNonQuery();
}
}
trans.Commit();
}
}
/// <summary>
/// Closes the connection to the database.
/// </summary>
public void Dispose()
{
if (!isDisposed)
this.connection.Close();
isDisposed = true;
}
}
}