diff --git a/src/Microsoft.Performance.SDK/Processing/Projection/ClipTimeToVisibleDomainProjection.cs b/src/Microsoft.Performance.SDK/Processing/Projection/ClipTimeToVisibleDomainProjection.cs index f539b159..67621ca4 100644 --- a/src/Microsoft.Performance.SDK/Processing/Projection/ClipTimeToVisibleDomainProjection.cs +++ b/src/Microsoft.Performance.SDK/Processing/Projection/ClipTimeToVisibleDomainProjection.cs @@ -73,6 +73,36 @@ public static IProjection Create(IProjection tim return (IProjection)instance; } + /// + /// Create a projection that is clipped to a + /// . + /// See also . + /// + /// + /// Original projection. + /// + /// + /// A visible domain sensitive projection. + /// + public static IProjection Create(IProjection resourceTimeRangeProjection) + { + Guard.NotNull(resourceTimeRangeProjection, nameof(resourceTimeRangeProjection)); + + var typeArgs = new[] + { + resourceTimeRangeProjection.GetType(), + }; + + var constructorArgs = new object[] + { + resourceTimeRangeProjection, + }; + + var type = typeof(ClipTimeToVisibleResourceTimeRangeDomainColumnGenerator<>).MakeGenericType(typeArgs); + var instance = Activator.CreateInstance(type, constructorArgs); + return (IProjection)instance; + } + /// /// Creates a new percent projection that is defined by the current viewport. /// @@ -101,6 +131,34 @@ public static IProjection CreatePercent(IProjection)instance; } + /// + /// Creates a new percent projection that is defined by the current viewport. + /// + /// + /// Original projection. + /// + /// + /// A viewport sensitive projection. + /// + public static IProjection CreatePercent(IProjection resourceTimeRangeColumn) + { + Guard.NotNull(resourceTimeRangeColumn, nameof(resourceTimeRangeColumn)); + + var typeArgs = new[] + { + resourceTimeRangeColumn.GetType(), + }; + + var constructorArgs = new object[] + { + resourceTimeRangeColumn, + }; + + var type = typeof(ClipTimeToVisibleResourceTimeRangePercentColumnGenerator<>).MakeGenericType(typeArgs); + var instance = Activator.CreateInstance(type, constructorArgs); + return (IProjection)instance; + } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] private struct ClipTimeToVisibleTimestampDomainColumnGenerator : IProjection, @@ -228,6 +286,72 @@ public bool NotifyVisibleDomainChanged(IVisibleDomainRegion visibleDomain) public bool DependsOnVisibleDomain => true; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + private struct ClipTimeToVisibleResourceTimeRangeDomainColumnGenerator + : IProjection, + IVisibleDomainSensitiveProjection + where TGenerator : IProjection + { + internal TGenerator Generator { get; } + + internal VisibleDomainRegionContainer VisibleDomainContainer { get; } + + public ClipTimeToVisibleResourceTimeRangeDomainColumnGenerator(TGenerator resourceTimeRangeGenerator) + { + this.Generator = resourceTimeRangeGenerator; + this.VisibleDomainContainer = new VisibleDomainRegionContainer(); + } + + // IProjection + public ResourceTimeRange this[int value] + { + get + { + TimeRange visibleDomain = this.VisibleDomainContainer.VisibleDomain; + + ResourceTimeRange resourceTimeRange = this.Generator[value]; + + resourceTimeRange.StartTime = ClipTimestampToVisibleDomain(resourceTimeRange.StartTime, visibleDomain); + resourceTimeRange.EndTime = ClipTimestampToVisibleDomain(resourceTimeRange.EndTime, visibleDomain); + + return resourceTimeRange; + } + } + + public Type SourceType + { + get + { + return typeof(int); + } + } + + public Type ResultType + { + get + { + return typeof(ResourceTimeRange); + } + } + + public object Clone() + { + var result = new ClipTimeToVisibleResourceTimeRangeDomainColumnGenerator( + VisibleDomainSensitiveProjection.CloneIfVisibleDomainSensitive(this.Generator)); + return result; + } + + + public bool NotifyVisibleDomainChanged(IVisibleDomainRegion visibleDomain) + { + this.VisibleDomainContainer.VisibleDomainRegion = visibleDomain; + VisibleDomainSensitiveProjection.NotifyVisibleDomainChanged(this.Generator, visibleDomain); + return true; + } + + public bool DependsOnVisibleDomain => true; + } + private struct ClipTimeToVisibleTimeRangePercentColumnGenerator : IProjection, IVisibleDomainSensitiveProjection, @@ -319,6 +443,98 @@ public string Format(string format, object arg, IFormatProvider formatProvider) } } } + + private struct ClipTimeToVisibleResourceTimeRangePercentColumnGenerator + : IProjection, + IVisibleDomainSensitiveProjection, + IFormatProvider + where TGenerator : IProjection + { + private readonly ClipTimeToVisibleResourceTimeRangeDomainColumnGenerator resourceTimeRangeColumnGenerator; + + // IFormatProvider returns an object - cannot return 'this' struct. + // So implement ICustomFormatter in a private class and return that object. + // + private readonly ClipTimeToVisibleResourceTimeRangePercentFormatProvider customFormatter; + + public ClipTimeToVisibleResourceTimeRangePercentColumnGenerator(TGenerator timeRangeGenerator) + { + var internalGenerator = new ClipTimeToVisibleResourceTimeRangeDomainColumnGenerator(timeRangeGenerator); + this.resourceTimeRangeColumnGenerator = internalGenerator; + + this.customFormatter = + new ClipTimeToVisibleResourceTimeRangePercentFormatProvider(() => internalGenerator.VisibleDomainContainer.VisibleDomain.Duration); + } + + public ResourceTimeRange this[int value] => this.resourceTimeRangeColumnGenerator[value]; + + public Type SourceType => typeof(int); + + public Type ResultType => typeof(ResourceTimeRange); + + public bool DependsOnVisibleDomain => true; + + public object GetFormat(Type formatType) + { + return (formatType == typeof(ResourceTimeRange) || + formatType == typeof(TimestampDelta)) ? this.customFormatter : null; + } + + public object Clone() + { + var result = new ClipTimeToVisibleResourceTimeRangePercentColumnGenerator( + VisibleDomainSensitiveProjection.CloneIfVisibleDomainSensitive(this.resourceTimeRangeColumnGenerator.Generator)); + return result; + } + + public bool NotifyVisibleDomainChanged(IVisibleDomainRegion visibleDomain) + { + this.resourceTimeRangeColumnGenerator.NotifyVisibleDomainChanged(visibleDomain); + return true; + } + + private class ClipTimeToVisibleResourceTimeRangePercentFormatProvider + : ICustomFormatter + { + private readonly Func getVisibleDomainDuration; + + public ClipTimeToVisibleResourceTimeRangePercentFormatProvider(Func getVisibleDomainDuration) + { + Guard.NotNull(getVisibleDomainDuration, nameof(getVisibleDomainDuration)); + + this.getVisibleDomainDuration = getVisibleDomainDuration; + } + + public string Format(string format, object arg, IFormatProvider formatProvider) + { + if (arg == null) + { + return string.Empty; + } + + TimestampDelta numerator; + if (arg is ResourceTimeRange) + { + numerator = ((ResourceTimeRange)arg).Duration; + } + else if (arg is TimestampDelta) + { + numerator = (TimestampDelta)arg; + } + else + { + throw new FormatException(); + } + + TimestampDelta visibleDomainDuration = getVisibleDomainDuration(); + double percent = (visibleDomainDuration != TimestampDelta.Zero) ? + (100.0 * ((double)numerator.ToNanoseconds) / (visibleDomainDuration.ToNanoseconds)) : + 100.0; + + return percent.ToString(format, formatProvider); + } + } + } } } } diff --git a/src/Microsoft.Performance.SDK/Processing/Projection/ViewportRelativePercentProjection.cs b/src/Microsoft.Performance.SDK/Processing/Projection/ViewportRelativePercentProjection.cs index 1d180671..2de67e2b 100644 --- a/src/Microsoft.Performance.SDK/Processing/Projection/ViewportRelativePercentProjection.cs +++ b/src/Microsoft.Performance.SDK/Processing/Projection/ViewportRelativePercentProjection.cs @@ -34,6 +34,54 @@ var columnRelativeToVisibleDomain return columnRelativeToVisibleDomain; } + /// + /// Creates a projection that returns the percentage of the visible time consumed by this + /// . + /// + /// + /// Time range. + /// + /// + /// Percent of time consumed by the given time range in a given visible domain. + /// + public static IProjection Create( + IProjection timeRangeColumn) + { + Guard.NotNull(timeRangeColumn, nameof(timeRangeColumn)); + + var aggregateRowsInVisibleDomainColumn + = Projection.AggregateInVisibleDomain(timeRangeColumn); + + var columnRelativeToVisibleDomain + = Percent.Create(timeRangeColumn.Compose(timeRange => timeRange.Duration), aggregateRowsInVisibleDomainColumn); + + return columnRelativeToVisibleDomain; + } + + /// + /// Creates a projection that returns the percentage of the visible time consumed by this + /// . + /// + /// + /// Resource time range. + /// + /// + /// Percent of time consumed by the given resource time range in a given visible domain. + /// + public static IProjection Create( + IProjection resourceTimeRangeColumn) + { + Guard.NotNull(resourceTimeRangeColumn, nameof(resourceTimeRangeColumn)); + + var aggregateRowsInVisibleDomainColumn + = Projection.AggregateInVisibleDomain(resourceTimeRangeColumn); + + var columnRelativeToVisibleDomain + = Percent.Create(resourceTimeRangeColumn.Compose(resourceTimeRange => resourceTimeRange.Duration), aggregateRowsInVisibleDomainColumn); + + return columnRelativeToVisibleDomain; + } + /// /// Creates a projection that maps a percentage relative to an entire domain to a percentage /// relative to the current visible domain. diff --git a/src/Microsoft.Performance.SDK/ResourceTimeRange.cs b/src/Microsoft.Performance.SDK/ResourceTimeRange.cs new file mode 100644 index 00000000..6c31e222 --- /dev/null +++ b/src/Microsoft.Performance.SDK/ResourceTimeRange.cs @@ -0,0 +1,428 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.Performance.SDK +{ + /// + /// Represents a range between two s on a resource with an id. + /// + [TypeConverter(typeof(ResourceTimeRangeConverter))] + [StructLayout(LayoutKind.Sequential)] + [DebuggerDisplay("{ResourceId}: {StartTime.ToNanoseconds}ns - {EndTime.ToNanoseconds}ns")] + public struct ResourceTimeRange + : IEquatable, + IFormatForClipboard, + IConvertible + { + private uint resourceId; + private TimeRange timeRange; + + /// + /// Gets or sets the resource id. + /// + public uint ResourceId + { + get + { + return this.resourceId; + } + + set + { + this.resourceId = value; + } + } + + /// + /// Gets or sets the time range. + /// + public TimeRange TimeRange + { + get + { + return this.timeRange; + } + + set + { + this.timeRange = value; + } + } + + /// + /// Gets or sets the start of the range. + /// + public Timestamp StartTime + { + get + { + return this.timeRange.StartTime; + } + + set + { + this.timeRange.StartTime = value; + } + } + + /// + /// Gets or sets the end of the range. + /// + public Timestamp EndTime + { + get + { + return this.timeRange.EndTime; + } + + set + { + this.timeRange.EndTime = value; + } + } + + /// + /// Determines whether the given instances are considered to be equal. + /// + /// + /// The first instance. + /// + /// + /// The second instance. + /// + /// + /// true if the instances are considered to be equal; + /// false otherwise. + /// + public static bool operator ==(ResourceTimeRange first, ResourceTimeRange second) + { + return first.Equals(second); + } + + /// + /// Determines whether the given instances are not considered to be equal. + /// + /// + /// The first instance. + /// + /// + /// The second instance. + /// + /// + /// true if the instances are not considered to be equal; + /// false otherwise. + /// + public static bool operator !=(ResourceTimeRange first, ResourceTimeRange second) + { + return !first.Equals(second); + } + + /// + public override bool Equals(object obj) + { + return (obj != null) && (obj is ResourceTimeRange resourceTimeRange) && Equals(resourceTimeRange); + } + + /// + public override int GetHashCode() + { + return HashCodeUtils.CombineHashCodeValues(this.resourceId.GetHashCode(), this.timeRange.GetHashCode()); + } + + /// + public bool Equals(ResourceTimeRange other) + { + return (this.resourceId == other.resourceId) && (this.timeRange == other.timeRange); + } + + /// + /// Gets the duration represented by this + /// as a . + /// + public TimestampDelta Duration + { + get + { + return this.timeRange.Duration; + } + } + + /// + /// Initializes a new instance of the + /// struct. + /// + /// + /// The resource id of the resource time range. + /// + /// + /// The time range of the resource time range. + /// + public ResourceTimeRange(uint resourceId, TimeRange timeRange) + { + this.resourceId = resourceId; + this.timeRange = timeRange; + } + + /// + /// Determines whether this instance contains the given . + /// + /// + /// The time to check. + /// + /// + /// true if is contained within this range; + /// false otherwise. + /// + public bool Contains(Timestamp time) + { + return this.timeRange.Contains(time); + } + + /// + /// Gets a representing no resource time. + /// + public static ResourceTimeRange Zero + { + get + { + return new ResourceTimeRange(0, TimeRange.Zero); + } + } + + /// + public override string ToString() + { + return ToString(new StringBuilder()).ToString(); + } + + private StringBuilder ToString(StringBuilder sb) + { + return sb.Append('[').Append(this.resourceId.ToString()).Append(", ").Append(this.timeRange.ToString()).Append(']'); + } + + /// + /// Parses the given string into a . + /// + /// + /// The string to parse. + /// + /// + /// The parsed + /// + /// + /// is null. + /// + /// + /// is not parseable into a . + /// + public static ResourceTimeRange Parse(string s) + { + if (s == null) + { + throw new ArgumentNullException("s"); + } + + ResourceTimeRange result; + if (!TryParse(s, out result)) + { + throw new FormatException(); + } + + return result; + } + + /// + /// Attempts to parse the given string into a . + /// + /// + /// The string to parse. + /// + /// + /// The parsed ; if successful. + /// + /// + /// true if parsing was successful; false + /// otherwise. + /// + public static bool TryParse(string s, out ResourceTimeRange result) + { + if (s == null) + { + throw new ArgumentNullException("s"); + } + + if (string.IsNullOrWhiteSpace(s)) + { + throw new ArgumentOutOfRangeException("s"); + } + + // "[0,0]" is the shortest string that would work. + if (s.Length < 5) + { + result = ResourceTimeRange.Zero; + return false; + } + + int leftBracketIndex = s.IndexOf('['); + if (leftBracketIndex == -1) + { + leftBracketIndex = s.IndexOf('('); + } + + int commaIndex = s.IndexOf(','); + int rightBracketIndex = s.LastIndexOf(']'); + if (rightBracketIndex == -1) + { + rightBracketIndex = s.LastIndexOf(')'); + } + + if (leftBracketIndex != 0 || commaIndex == -1 || rightBracketIndex != (s.Length - 1)) + { + result = ResourceTimeRange.Zero; + return false; + } + + string beginStr = s.Substring(leftBracketIndex + 1, commaIndex - leftBracketIndex - 1); + string endStr = s.Substring(commaIndex + 1, rightBracketIndex - commaIndex - 1); + + uint resourceId; + TimeRange timeRange; + + if (!uint.TryParse(beginStr, out resourceId) || !TimeRange.TryParse(endStr, out timeRange)) + { + result = ResourceTimeRange.Zero; + return false; + } + + result = new ResourceTimeRange(resourceId, timeRange); + return true; + } + + /// + string IFormatForClipboard.ToClipboardString(string format, bool includeUnits) + { + return TimestampFormatter.ToClipboardString(this.Duration.ToNanoseconds, format, null, includeUnits); + } + + /// + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + if (conversionType == typeof(TimestampDelta)) + { + return this.Duration; + } + else if (conversionType == typeof(ResourceTimeRange)) // needed since Convert.ToType goes down this code-path + { + return this; + } + else + { + throw new InvalidCastException(); + } + } + + /// + TypeCode IConvertible.GetTypeCode() + { + return TypeCode.Object; + } + + /// + bool IConvertible.ToBoolean(IFormatProvider provider) + { + return Convert.ToBoolean(this.Duration.ToNanoseconds); + } + + /// + byte IConvertible.ToByte(IFormatProvider provider) + { + return Convert.ToByte(this.Duration.ToNanoseconds); + } + + /// + char IConvertible.ToChar(IFormatProvider provider) + { + return Convert.ToChar(this.Duration.ToNanoseconds); + } + + /// + DateTime IConvertible.ToDateTime(IFormatProvider provider) + { + return Convert.ToDateTime(this.Duration.ToNanoseconds); + } + + /// + decimal IConvertible.ToDecimal(IFormatProvider provider) + { + return this.Duration.ToNanoseconds; + } + + /// + double IConvertible.ToDouble(IFormatProvider provider) + { + return Convert.ToDouble(this.Duration.ToNanoseconds); + } + + /// + short IConvertible.ToInt16(IFormatProvider provider) + { + return Convert.ToInt16(this.Duration.ToNanoseconds); + } + + /// + int IConvertible.ToInt32(IFormatProvider provider) + { + return Convert.ToInt32(this.Duration.ToNanoseconds); + } + + /// + long IConvertible.ToInt64(IFormatProvider provider) + { + return Convert.ToInt64(this.Duration.ToNanoseconds); + } + + /// + sbyte IConvertible.ToSByte(IFormatProvider provider) + { + return Convert.ToSByte(this.Duration.ToNanoseconds); + } + + /// + float IConvertible.ToSingle(IFormatProvider provider) + { + return Convert.ToSingle(this.Duration.ToNanoseconds); + } + + /// + string IConvertible.ToString(IFormatProvider provider) + { + return this.ToString(); + } + + /// + ushort IConvertible.ToUInt16(IFormatProvider provider) + { + return Convert.ToUInt16(this.Duration.ToNanoseconds); + } + + /// + uint IConvertible.ToUInt32(IFormatProvider provider) + { + return Convert.ToUInt32(this.Duration.ToNanoseconds); + } + + /// + ulong IConvertible.ToUInt64(IFormatProvider provider) + { + return Convert.ToUInt64(this.Duration.ToNanoseconds); + } + } +} diff --git a/src/Microsoft.Performance.SDK/ResourceTimeRangeConverter.cs b/src/Microsoft.Performance.SDK/ResourceTimeRangeConverter.cs new file mode 100644 index 00000000..0e8ade1a --- /dev/null +++ b/src/Microsoft.Performance.SDK/ResourceTimeRangeConverter.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.Globalization; + +namespace Microsoft.Performance.SDK +{ + /// + /// Converts instances to instances. + /// + public sealed class ResourceTimeRangeConverter + : TypeConverter, + ICustomTypeConverter + { + /// + /// Initializes a new instance of the + /// class. + /// + public ResourceTimeRangeConverter() + { + } + + /// + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + { + return true; + } + + return base.CanConvertFrom(context, sourceType); + } + + /// + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return base.CanConvertTo(context, destinationType); + } + + /// + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + string asString = value as string; + if (asString != null) + { + ResourceTimeRange resourceTimeRange; + + if (ResourceTimeRange.TryParse(asString.Trim(), out resourceTimeRange)) + { + return resourceTimeRange; + } + } + + return base.ConvertFrom(context, culture, value); + } + + /// + public override object ConvertTo( + ITypeDescriptorContext context, + CultureInfo culture, + object value, + Type destinationType) + { + if (destinationType == null) + { + throw new ArgumentNullException("destinationType"); + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + /// + public Type GetOutputType() + { + return typeof(ResourceTimeRange); + } + } +} diff --git a/src/Microsoft.Performance.SDK/TimeFormatProviders.cs b/src/Microsoft.Performance.SDK/TimeFormatProviders.cs index 35607778..33217327 100644 --- a/src/Microsoft.Performance.SDK/TimeFormatProviders.cs +++ b/src/Microsoft.Performance.SDK/TimeFormatProviders.cs @@ -8,7 +8,7 @@ namespace Microsoft.Performance.SDK { /// /// Abstract helper for Time based objects - /// , , . + /// , , , . /// /// [DefaultFormat(TimestampFormatter.FormatSecondsGrouped)] @@ -38,6 +38,11 @@ public string Format(string format, object arg, IFormatProvider formatProvider) var timeRange = (TimeRange)arg; return TimestampFormatter.ToString(timeRange.Duration.ToNanoseconds, format, formatProvider); } + else if (arg is ResourceTimeRange) + { + var resourceTimeRange = (ResourceTimeRange)arg; + return TimestampFormatter.ToString(resourceTimeRange.Duration.ToNanoseconds, format, formatProvider); + } else if (arg is TimestampDelta) { var timestampDelta = (TimestampDelta)arg; @@ -75,4 +80,10 @@ public sealed class TimestampDeltaFormatProvider : TimeFormatProvider { } /// /// public sealed class TimeRangeFormatProvider : TimeFormatProvider { } + + /// + /// Format Provider for . + /// + /// + public sealed class ResourceTimeRangeFormatProvider : TimeFormatProvider { } }