Initial Commit

master
Harald Wolff 2019-03-09 19:45:09 +01:00
commit 415e3e24c6
10 changed files with 744 additions and 0 deletions

41
.gitignore vendored 100644
View File

@ -0,0 +1,41 @@
# Autosave files
*~
# build
[Oo]bj/
[Bb]in/
packages/
TestResults/
# globs
Makefile.in
*.DS_Store
*.sln.cache
*.suo
*.cache
*.pidb
*.userprefs
*.usertasks
config.log
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.user
*.tar.gz
tarballs/
test-results/
Thumbs.db
.vs/
# Mac bundle stuff
*.dmg
*.app
# resharper
*_Resharper.*
*.Resharper
# dotCover
*.dotCover

View File

@ -0,0 +1,35 @@
// /**
// * File: AssemblyInfo.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("ln.perfdb")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("${AuthorCopyright}")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.0.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]

52
ln.perfdb.csproj 100644
View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{D934C1E8-9215-4D8D-83B3-A296E4912792}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>ln.perfdb</RootNamespace>
<AssemblyName>ln.perfdb</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="storage\PerfTable.cs" />
<Compile Include="storage\PerfFile.cs" />
<Compile Include="storage\SectionSpecification.cs" />
<Compile Include="storage\ShortRead.cs" />
<Compile Include="storage\PerfValue.cs" />
<Compile Include="storage\Aggregates.cs" />
<Compile Include="storage\PerfFile.PerfFileSection.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="storage\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ln.logging\ln.logging.csproj">
<Project>{D471A566-9FB6-41B2-A777-3C32874ECD0E}</Project>
<Name>ln.logging</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,56 @@
// /**
// * File: Aggregates.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
namespace ln.perfdb.storage
{
public static class Aggregates
{
public static PerfValue Aggregate(PerfValue[] perfValues,AggregationMethod method)
{
switch (method)
{
case AggregationMethod.AVERAGE:
return Average(perfValues);
default:
return Average(perfValues);
}
}
public static PerfValue Average(PerfValue[] perfValues)
{
PerfValue result = new PerfValue();
int numValues = 0;
foreach (PerfValue perfValue in perfValues)
{
if (perfValue.TimeStamp != 0)
{
result.Value += perfValue.Value;
result.TimeStamp += perfValue.TimeStamp;
numValues++;
}
}
if (numValues > 0)
{
result.Value /= numValues;
result.TimeStamp /= numValues;
}
else
{
result.TimeStamp = 0;
}
return result;
}
}
}

View File

@ -0,0 +1,307 @@
// /**
// * File: PerfFile.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.IO;
using ln.logging;
namespace ln.perfdb.storage
{
public partial class PerfFile
{
public class PerfFileSection
{
/**
* Binary Section Header Format
*
* Offset Field
* ---------------------------------
* 0x00 Flags (not in use)
* 0x04 nRecords
* 0x08 tWindow
* 0x0C AggregationMethod
* 0x10 LastTimeStamp (64bit)
*
* 0x18 not in use
* 0x1C not in use
*
**/
public PerfFile PerfFile { get; private set; }
public PerfFileSection ParentSection { get; private set; }
public int Offset { get; set; }
public int DataOffset => Offset + 32;
public int NextOffset => DataOffset + (nRecords * 16);
public PerfFileSection NextSection { get; private set; }
public PerfFileSection LastSection => NextSection != null ? NextSection : this;
public int nRecords { get; private set; }
public int tWindow { get; private set; }
public int TimeSpan => nRecords * tWindow;
public long LastRecord { get; set; }
public AggregationMethod AggregationMethod { get; private set; }
public FileStream FileStream => PerfFile.FileStream;
public int AggregateSize => NextSection == null ? 0 : (NextSection.tWindow / tWindow);
public int AggregateGroups => AggregateSize > 0 ? nRecords / AggregateSize : 0;
internal PerfFileSection(PerfFile perfFile, PerfFileSection parent)
{
PerfFile = perfFile;
ParentSection = parent;
Offset = ParentSection != null ? ParentSection.NextOffset : 0x10;
Logging.Log(LogLevel.DEBUGDETAIL, "PerfFileSection: loading at offset 0x{0:x8}", Offset);
byte[] header = new byte[32];
FileStream.Position = Offset;
int nread = perfFile.FileStream.Read(header, 0, 32);
if (nread != 32)
throw new ShortRead();
nRecords = BitConverter.ToInt32(header, 0x04);
tWindow = BitConverter.ToInt32(header, 0x08);
AggregationMethod = (AggregationMethod)BitConverter.ToInt32(header, 0x0C);
LastRecord = BitConverter.ToInt32(header, 0x10);
Logging.Log(LogLevel.DEBUGDETAIL, "PerfFileSection: Loaded: {0}", this);
if (FileStream.Length > NextOffset)
{
NextSection = new PerfFileSection(PerfFile, this);
}
}
public PerfFileSection(PerfFile perfFile, PerfFileSection parent, int nRecords, int tWindow, AggregationMethod aggregationMethod)
{
PerfFile = perfFile;
ParentSection = parent;
if (parent != null)
{
if (parent.NextSection != null)
{
throw new ArgumentException("Parent already has NextSection", nameof(parent));
}
ParentSection.NextSection = this;
Offset = ParentSection.NextOffset;
if (((tWindow / parent.tWindow) * parent.tWindow) != tWindow)
throw new ArgumentException("tWindow needs to be integer divideable with tWindow of parent", nameof(tWindow));
}
else
{
if (perfFile.FirstSection != null)
throw new ArgumentException("PerfFile already has a Section", nameof(perfFile));
perfFile.FirstSection = this;
Offset = 0x10;
}
this.tWindow = tWindow;
this.nRecords = nRecords;
this.AggregationMethod = aggregationMethod;
LastRecord = -1;
if (FileStream.Length < NextOffset)
FileStream.SetLength(NextOffset);
byte[] sectionHeader = new byte[0x20];
Array.Copy(BitConverter.GetBytes((int)0), 0, sectionHeader, 0x00, 4);
Array.Copy(BitConverter.GetBytes(nRecords), 0, sectionHeader, 0x04, 4);
Array.Copy(BitConverter.GetBytes(tWindow), 0, sectionHeader, 0x08, 4);
Array.Copy(BitConverter.GetBytes((int)AggregationMethod), 0, sectionHeader, 0x0C, 4);
Array.Copy(BitConverter.GetBytes(LastRecord), 0, sectionHeader, 0x10, 8);
FileStream.Position = Offset;
FileStream.Write(sectionHeader, 0, 0x20);
FileStream.Flush();
Logging.Log(LogLevel.DEBUGDETAIL, "PerfFileSection: Created: {0}", this);
}
public void Sync()
{
FileStream.Position = Offset + 0x10;
FileStream.Write(BitConverter.GetBytes(LastRecord), 0, 8);
if (NextSection != null)
NextSection.Sync();
}
private long GetRecord(long timeStamp)
{
return timeStamp / tWindow;
}
private int GetSlot(long rawRecord)
{
return (int)(rawRecord % nRecords);
}
private int GetAggregateGroup(int slot)
{
return slot / AggregateSize;
}
private void Aggregate(int group)
{
if (NextSection != null)
{
PerfValue[] perfValues = new PerfValue[AggregateSize];
ReadRecords(perfValues, 0, group * AggregateSize, AggregateSize);
NextSection.WriteRecord(Aggregates.Aggregate(perfValues, NextSection.AggregationMethod));
for (int n = 0; n < AggregateSize; n++)
{
ClearRecord((group * AggregateSize) + n);
}
}
}
public void ClearRecord(int nRecord)
{
byte[] binaryRecord = new byte[16];
int thisSlot = GetSlot(nRecord);
FileStream.Position = DataOffset + (thisSlot * 16);
FileStream.Write(binaryRecord, 0, 16);
}
public void WriteRecord(PerfValue perfValue)
{
if (perfValue.TimeStamp >= LastRecord)
{
long thisRecord = GetRecord(perfValue.TimeStamp);
int thisSlot = GetSlot(thisRecord);
if ((NextSection != null) && (LastRecord > 0))
{
int lastAGroup = GetAggregateGroup(GetSlot(LastRecord));
int thisAGroup = GetAggregateGroup(thisSlot);
if ((lastAGroup != thisAGroup) || ((thisRecord - LastRecord) > nRecords))
{
int ag = (thisRecord - LastRecord) > nRecords ? thisAGroup : lastAGroup;
do
{
ag++;
ag %= AggregateGroups;
Aggregate(ag);
} while (ag != thisAGroup);
}
}
FileStream.Position = DataOffset + (thisSlot * 16);
FileStream.Write(BitConverter.GetBytes(perfValue.TimeStamp), 0, 8);
FileStream.Write(BitConverter.GetBytes(perfValue.Value), 0, 8);
LastRecord = thisRecord;
}
}
public void ReadRecords(PerfValue[] target, int offset, int firstRecord, int numRecords)
{
//Logging.Log(LogLevel.DEBUG, "PerfFileSection: Read(<resultbuffer>,{0},{1},{2})",offset,firstRecord,numRecords);
byte[] binaryRow = new byte[16];
for (int n = 0; n < numRecords; n++)
{
int nRow = (firstRecord + n) % nRecords;
FileStream.Position = DataOffset + (nRow * 16);
int nread = FileStream.Read(binaryRow, 0, 16);
if (nread != 16)
throw new ShortRead();
long rTimeStamp = BitConverter.ToInt64(binaryRow, 0);
double rValue = BitConverter.ToDouble(binaryRow, 8);
if (rTimeStamp == 0)
{
target[offset + n] = new PerfValue();
}
else
{
target[offset + n] = new PerfValue(rTimeStamp, rValue);
}
}
}
public PerfValue[] Query(int queryRecords, int skipRecords, int nTrail)
{
Logging.Log(LogLevel.DEBUG, "PerfFileSection.Query({0},{1},{2})", queryRecords, skipRecords, nTrail);
PerfValue[] result;
int maxQueryRecords = (skipRecords > nRecords) ? 0 : nRecords - skipRecords;
int mySkipRecords = (skipRecords > nRecords) ? nRecords : skipRecords;
int nextSkipRecords = (skipRecords > nRecords) ? skipRecords - nRecords : 0;
int myQueryRecords = (maxQueryRecords > queryRecords) ? queryRecords : maxQueryRecords;
int nextQueryRecords = queryRecords - myQueryRecords;
int myFirstRecord = (int)(LastRecord - mySkipRecords - myQueryRecords) + 1;
int myFirstSlot = GetSlot(myFirstRecord);
int myFirstAG = GetAggregateGroup(myFirstSlot);
int myLastAG = GetAggregateGroup(GetSlot(LastRecord));
if (myLastAG == myFirstAG)
{
int myPreSkip = AggregateSize - (myFirstRecord % AggregateSize);
myQueryRecords -= myPreSkip;
myFirstRecord += myPreSkip;
}
if ((nextQueryRecords == 0) || (NextSection == null))
{
result = new PerfValue[nTrail + myQueryRecords];
}
else
{
nextQueryRecords /= AggregateSize;
nextSkipRecords /= AggregateSize;
result = NextSection.Query(nextQueryRecords,nextSkipRecords,nTrail + myQueryRecords);
}
int myResultOffset = (result.Length - nTrail - myQueryRecords);
Logging.Log(LogLevel.DEBUG, "PerfFileSection.Query(): {0}", this);
Logging.Log(LogLevel.DEBUG, "PerfFileSection.Query(): Loading {0} records starting at {1}", myQueryRecords, myFirstRecord);
ReadRecords(result, myResultOffset, myFirstRecord, myQueryRecords);
return result;
}
public override string ToString()
{
return String.Format("[PerfFileSection Offset=0x{0:x8} NextOffset={1:x8} nRecords={2} tWindow={3} TimeSpan={4} LastRecord={5}({6})]", Offset, NextOffset, nRecords, tWindow, new TimeSpan(0, 0, TimeSpan), LastRecord, GetSlot(LastRecord));
}
}
}
}

157
storage/PerfFile.cs 100644
View File

@ -0,0 +1,157 @@
// /**
// * File: PerfFile.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.IO;
using ln.logging;
using System.Runtime.Remoting.Messaging;
using System.Text;
namespace ln.perfdb.storage
{
/**
* PerfFile Format Specification
*
* File Header:
*
* 0x0000 4 MAGIC
* 0x0004 4 Version
* 0x0008 4 nSections
* 0x000C 4 RESERVE
* 0x0010 16 Section specification #0
*[0x0020 16 Section specification #1]
*[...]
* Section Data #0
*[Section Data #1]
*
* Section specification format:
* 0x0000 4 Flags
* 0x0004 4 nRecords
* 0x0008 4 tWindow
* 0x000C 4 Aggregation Method
*
**/
public partial class PerfFile
{
public readonly ulong Magic1 = 0xBEAF8888BEAF8888L;
public readonly ulong Magic2 = 0xBAD80000BAD80000L;
public readonly int SecondsPerHour = 3600;
public readonly int SecondsPerDay = 3600 * 24;
public readonly int SecondsPerWeek = 3600 * 24 * 7;
public String FileName { get; }
public FileStream FileStream { get; private set; }
public PerfFileSection FirstSection { get; private set; }
public PerfFile(String filename)
{
FileName = filename;
}
private void Initialize()
{
if (FileStream.Length == 0)
{
FileStream.Write(BitConverter.GetBytes(Magic1), 0, 8);
FileStream.Write(BitConverter.GetBytes(Magic2), 0, 8);
FileStream.Flush();
FirstSection = null;
Logging.Log(LogLevel.DEBUG, "PerfTable: created {0}", FileName);
}
else
{
byte[] magics = new byte[16];
FileStream.Position = 0;
int nread = FileStream.Read(magics, 0, 16);
if (nread != 16)
throw new ShortRead();
ulong m1, m2;
m1 = BitConverter.ToUInt64(magics, 0);
m2 = BitConverter.ToUInt64(magics, 8);
if ((m1 != Magic1) || (m2 != Magic2))
throw new FormatException("Magic numbers do not match");
FirstSection = new PerfFileSection(this, null);
}
Logging.Log(LogLevel.DEBUG, "PerfFile: Initialized for filename {0}", FileName);
long timespan = 0;
for (PerfFileSection section = FirstSection; section != null; section = section.NextSection)
{
Logging.Log(LogLevel.DEBUGDETAIL, " Section: nRecords={0,6} tWindow={1} TimeSpan={2} TimeInPast={3}", section.nRecords,new TimeSpan(0,0,section.tWindow),new TimeSpan(0,0,section.TimeSpan),new TimeSpan(0,0,(int)timespan));
timespan += section.TimeSpan;
}
}
public bool IsOpen => (FileStream != null);
public void Open()
{
lock (this)
{
if (IsOpen)
throw new IOException("Already opened");
FileStream = new FileStream(this.FileName, FileMode.OpenOrCreate);
Initialize();
}
}
public void Close()
{
lock (this)
{
if (!IsOpen)
throw new IOException("not opened");
if (FirstSection != null)
FirstSection.Sync();
FirstSection = null;
FileStream.Close();
FileStream = null;
}
}
public void Write(long timestamp,double value)
{
Write(new PerfValue(timestamp, value));
}
public void Write(PerfValue perfValue)
{
FirstSection.WriteRecord(perfValue);
}
/**
* QueryTime(): Query Results for timeWindow [s] before skipTime [s]
**/
public PerfValue[] QueryTime(int timeWindow, int skipTime)
{
int queryRecords = timeWindow / FirstSection.tWindow;
int skipRecords = skipTime / FirstSection.tWindow;
return Query(queryRecords, skipRecords);
}
public PerfValue[] Query(int queryRecords,int skipRecords)
{
return FirstSection.Query(queryRecords, skipRecords, 0);
}
}
}

View File

@ -0,0 +1,25 @@
// /**
// * File: PerfTable.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
namespace ln.perfdb.storage
{
public struct RetentionInterval
{
public int CheckInterval;
public int RetentionAge;
}
public class PerfTable
{
public PerfTable()
{
}
}
}

View File

@ -0,0 +1,29 @@
// /**
// * File: PerfValue.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
namespace ln.perfdb.storage
{
public struct PerfValue
{
public long TimeStamp;
public double Value;
public PerfValue(long timeStamp, double value)
{
TimeStamp = timeStamp;
Value = value;
}
public override string ToString()
{
return String.Format("Timestamp: {0,12} {1}",TimeStamp,Value);
}
}
}

View File

@ -0,0 +1,18 @@
using System;
namespace ln.perfdb.storage
{
public enum AggregationMethod : int {
AVERAGE = 0x00,
MIN = 0x01,
MAX = 0x02,
STEP = 0x04
}
public struct SectionSpecification
{
public int Flags;
public int nRecords;
public int tWindow;
public AggregationMethod AggregationMethod;
}
}

View File

@ -0,0 +1,24 @@
// /**
// * File: ShortRead.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.IO;
namespace ln.perfdb.storage
{
public class ShortRead : IOException
{
public ShortRead()
{
}
public ShortRead(String message)
:base(message)
{
}
}
}