##// END OF EJS Templates
Implab.Diagnostics drafts
cin -
r253:34df34841225 v3.0.1-beta v3
parent child
Show More
@@ -0,0 +1,29
1 using System;
2 using System.Diagnostics;
3
4 namespace Implab.Diagnostics {
5 public class ActivityScope : IDisposable {
6 readonly TraceSource m_source;
7
8 readonly Guid m_prevId;
9
10 readonly string m_activity;
11
12 readonly int m_code;
13
14 internal ActivityScope(TraceSource source, Guid prevId, int code, string activity) {
15 m_source = source;
16 m_prevId = prevId;
17 m_code = code;
18 m_activity = activity;
19 }
20
21
22 public void Dispose() {
23 if (Trace.CorrelationManager.ActivityId != m_prevId)
24 m_source.TraceTransfer(m_code, "Transfer", m_prevId);
25 m_source.TraceEvent(TraceEventType.Stop, 0, m_activity);
26 Trace.CorrelationManager.ActivityId = m_prevId;
27 }
28 }
29 } No newline at end of file
@@ -0,0 +1,17
1 using System;
2 using System.Diagnostics;
3
4 namespace Implab.Diagnostics {
5 public class LogicalOperation {
6 public Stopwatch OperationStopwatch { get; private set; }
7
8 public string Name { get; private set; }
9
10 internal LogicalOperation(string name) {
11 Name = string.IsNullOrEmpty(name) ? "<unnamed>" : name;
12 OperationStopwatch = Stopwatch.StartNew();
13 }
14
15 public override string ToString() => Name;
16 }
17 } No newline at end of file
@@ -0,0 +1,21
1 using System;
2 using System.Diagnostics;
3
4 namespace Implab.Diagnostics {
5 public class LogicalOperationScope : IDisposable {
6 readonly TraceSource m_source;
7
8 readonly LogicalOperation m_operation;
9
10 internal LogicalOperationScope(TraceSource source, LogicalOperation operation) {
11 m_source = source;
12 m_operation = operation;
13 }
14
15 public void Dispose() {
16 m_operation.OperationStopwatch.Stop();
17 Trace.CorrelationManager.StopLogicalOperation();
18 m_source.TraceData(TraceEventType.Information, TraceEventCodes.StopLogicalOperation, m_operation);
19 }
20 }
21 } No newline at end of file
@@ -0,0 +1,114
1 using System;
2 using System.Diagnostics;
3 using System.IO;
4
5 namespace Implab.Diagnostics {
6 public class SimpleTraceListener : TextWriterTraceListener {
7 public SimpleTraceListener() {
8 }
9
10 public SimpleTraceListener(Stream stream) : base(stream) {
11 }
12
13 public SimpleTraceListener(TextWriter writer) : base(writer) {
14 }
15
16 public SimpleTraceListener(string fileName) : base(fileName) {
17 }
18
19 public SimpleTraceListener(Stream stream, string name) : base(stream, name) {
20 }
21
22 public SimpleTraceListener(TextWriter writer, string name) : base(writer, name) {
23 }
24
25 public SimpleTraceListener(string fileName, string name) : base(fileName, name) {
26 }
27
28 public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data) {
29 switch (id) {
30 case TraceEventCodes.StartLogicalOperation:
31 TraceEvent(eventCache, source, eventType, id, "+{0}", data);
32 break;
33 case TraceEventCodes.StopLogicalOperation:
34 TraceEvent(eventCache, source, eventType, id, FormatStopLogicalOperation(data));
35 break;
36 default:
37 TraceEvent(eventCache, source, eventType, id, data?.ToString());
38 break;
39 }
40 }
41
42 string FormatStopLogicalOperation(object data) {
43 if (data is LogicalOperation op) {
44 return string.Format("-{0} ({1})", op, FormatTimespan(op.OperationStopwatch.Elapsed));
45 } else {
46 return data?.ToString();
47 }
48 }
49
50 string FormatTimespan(TimeSpan value) {
51 if (value.TotalSeconds < 10) {
52 return value.Milliseconds.ToString() + "ms";
53 } else if (value.TotalSeconds < 30) {
54 return string.Format("{0:0.###}s", value.TotalSeconds);
55 } else {
56 return value.ToString();
57 }
58 }
59
60 public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, params object[] data) {
61 var prev = IndentLevel;
62 IndentLevel += eventCache.LogicalOperationStack.Count;
63 try {
64 base.TraceData(eventCache, source, eventType, id, data);
65 } finally {
66 IndentLevel = prev;
67 }
68 }
69
70 public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id) {
71 var prev = IndentLevel;
72 IndentLevel += eventCache.LogicalOperationStack.Count;
73 try {
74 base.TraceEvent(eventCache, source, eventType, id);
75 } finally {
76 IndentLevel = prev;
77 }
78 }
79
80 public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args) {
81 TraceEvent(eventCache, source, eventType, id, String.Format(format, args));
82 }
83
84 public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message) {
85 var prev = IndentLevel;
86 IndentLevel += eventCache.LogicalOperationStack.Count;
87 try {
88 LogicalOperation operation = null;
89 if (eventCache.LogicalOperationStack.Count > 0)
90 operation = eventCache.LogicalOperationStack.Peek() as LogicalOperation;
91
92 if (operation != null) {
93 base.TraceData(eventCache, source, eventType, id, FormatTimespan(operation.OperationStopwatch.Elapsed) + ": " + message);
94 } else {
95 base.TraceData(eventCache, source, eventType, id, message);
96 }
97 } finally {
98 IndentLevel = prev;
99 }
100 }
101
102 public override void TraceTransfer(TraceEventCache eventCache, string source, int id, string message, Guid relatedActivityId) {
103 var prev = IndentLevel;
104 IndentLevel += eventCache.LogicalOperationStack.Count;
105 try {
106 base.TraceTransfer(eventCache, source, id, message, relatedActivityId);
107 } finally {
108 IndentLevel = prev;
109 }
110 }
111
112
113 }
114 } No newline at end of file
@@ -0,0 +1,10
1 namespace Implab.Diagnostics {
2 public class TraceEventCodes {
3 public const int EventCodesBase = 1024;
4
5 public const int StartLogicalOperation = EventCodesBase + 1;
6
7 public const int StopLogicalOperation = EventCodesBase + 2;
8
9 }
10 } No newline at end of file
@@ -1,37 +1,29
1 using System;
1 using System;
2 using System.Diagnostics;
2 using System.Diagnostics;
3 using System.Threading;
3 using System.Threading;
4 using Implab.Diagnostics;
4 using Implab.Diagnostics;
5 using Xunit;
5 using Xunit;
6
6
7 namespace Implab.Test
7 namespace Implab.Test {
8 {
8 using System.Threading.Tasks;
9 using static Trace<UnitTest1>;
9 using static Trace<UnitTest1>;
10 public class UnitTest1
10 public class UnitTest1 {
11 {
12 [Fact]
11 [Fact]
13 public void Test1()
12 public async Task Test1() {
14 {
13 var listener = new SimpleTraceListener(Console.Out);
15 var listener = new TextWriterTraceListener(Console.Out);
14
16 var source = TraceSource;
15 var source = TraceSource;
17 source.Switch.Level = SourceLevels.All;
16 source.Switch.Level = SourceLevels.All;
18
17
19 source.Listeners.Add(listener);
18 source.Listeners.Add(listener);
20 Trace.Listeners.Add(listener);
21
19
22 Trace.WriteLine("Hello!");
20 using (var op = LogicalOperation(nameof(Test1)))
23 StartLogicalOperation();
21 using (LogicalOperation("InnerOperation")){
24
22 await Task.Yield();
25 Trace.WriteLine("Inner");
23 Log("Inner");
26 foreach(var x in Trace.CorrelationManager.LogicalOperationStack)
24 await Task.Yield();
27 Trace.WriteLine($"-{x}");
25 Log("source event");
28 Log("source event");
26 }
29
30 listener.IndentLevel = 1;
31
32 source.TraceData(TraceEventType.Start, 1, DateTime.Now);
33
34 StopLogicalOperation();
35 }
27 }
36 }
28 }
37 }
29 }
@@ -1,130 +1,130
1 using System;
1 using System;
2 using Implab.Parallels;
2 using Implab.Parallels;
3 using System.Threading;
3 using System.Threading;
4 using System.Reflection;
4 using System.Reflection;
5 using System.Diagnostics;
5 using System.Diagnostics;
6
6
7 namespace Implab {
7 namespace Implab {
8 /// <summary>
8 /// <summary>
9 /// Abstract class for creation of custom one-shot thread safe events.
9 /// Abstract class for creation of custom one-shot thread safe events.
10 /// </summary>
10 /// </summary>
11 /// <remarks>
11 /// <remarks>
12 /// <para>
12 /// <para>
13 /// An event is something that should happen in the future and the
13 /// An event is something that should happen in the future and the
14 /// triggering of the event causes execution of some pending actions
14 /// triggering of the event causes execution of some pending actions
15 /// which are formely event handlers. One-shot events occur only once
15 /// which are formely event handlers. One-shot events occur only once
16 /// and any handler added after the event is triggered should run
16 /// and any handler added after the event is triggered should run
17 /// without a delay.
17 /// without a delay.
18 /// </para>
18 /// </para>
19 /// <para>
19 /// <para>
20 /// The lifecycle of the one-shot event is tipically consists of following
20 /// The lifecycle of the one-shot event is tipically consists of following
21 /// phases.
21 /// phases.
22 /// <list>
22 /// <list>
23 /// <description>Pending state. This is the initial state of the event. Any
23 /// <description>Pending state. This is the initial state of the event. Any
24 /// handler added to the event will be queued for the future execution.
24 /// handler added to the event will be queued for the future execution.
25 /// </description>
25 /// </description>
26 /// <description>Transitional state. This is intermediate state between pending
26 /// <description>Transitional state. This is intermediate state between pending
27 /// and fulfilled states, during this state internal initialization and storing
27 /// and fulfilled states, during this state internal initialization and storing
28 /// of the result occurs.
28 /// of the result occurs.
29 /// </description>
29 /// </description>
30 /// <description>Fulfilled state. The event contains the result, all queued
30 /// <description>Fulfilled state. The event contains the result, all queued
31 /// handlers are signalled to run and newly added handlers are executed
31 /// handlers are signalled to run and newly added handlers are executed
32 /// immediatelly.
32 /// immediatelly.
33 /// </description>
33 /// </description>
34 /// </list>
34 /// </list>
35 /// </para>
35 /// </para>
36 /// </remarks>
36 /// </remarks>
37 public abstract class AbstractEvent<THandler> where THandler : class {
37 public abstract class AbstractEvent<THandler> where THandler : class {
38 const int PendingState = 0;
38 const int PendingState = 0;
39
39
40 const int TransitionalState = 1;
40 const int TransitionalState = 1;
41
41
42 const int ResolvedState = 2;
42 const int ResolvedState = 2;
43
43
44 volatile int m_state;
44 volatile int m_state;
45
45
46 THandler m_handler;
46 THandler m_handler;
47 SimpleAsyncQueue<THandler> m_extraHandlers;
47 SimpleAsyncQueue<THandler> m_extraHandlers;
48
48
49 public bool IsResolved {
49 public bool IsResolved {
50 get {
50 get {
51 return m_state > TransitionalState;
51 return m_state > TransitionalState;
52 }
52 }
53 }
53 }
54
54
55 #region state managment
55 #region state managment
56 protected bool BeginTransit() {
56 protected bool BeginTransit() {
57 return PendingState == Interlocked.CompareExchange(ref m_state, TransitionalState, PendingState);
57 return PendingState == Interlocked.CompareExchange(ref m_state, TransitionalState, PendingState);
58 }
58 }
59
59
60 protected void CompleteTransit() {
60 protected void CompleteTransit() {
61 #if DEBUG
61 #if DEBUG
62 if (TransitionalState != Interlocked.CompareExchange(ref m_state, ResolvedState, TransitionalState))
62 if (TransitionalState != Interlocked.CompareExchange(ref m_state, ResolvedState, TransitionalState))
63 throw new InvalidOperationException("Can't complete transition when the object isn't in the transitional state");
63 throw new InvalidOperationException("Can't complete transition when the object isn't in the transitional state");
64 #else
64 #else
65 m_state = state;
65 m_state = ResolvedState;
66 #endif
66 #endif
67 Signal();
67 Signal();
68 }
68 }
69
69
70 protected void WaitTransition() {
70 protected void WaitTransition() {
71 if (m_state == TransitionalState) {
71 if (m_state == TransitionalState) {
72 SpinWait spin = new SpinWait();
72 SpinWait spin = new SpinWait();
73 do {
73 do {
74 spin.SpinOnce();
74 spin.SpinOnce();
75 } while (m_state == TransitionalState);
75 } while (m_state == TransitionalState);
76 }
76 }
77 }
77 }
78
78
79
79
80 protected abstract void SignalHandler(THandler handler);
80 protected abstract void SignalHandler(THandler handler);
81
81
82 void Signal() {
82 void Signal() {
83 THandler handler;
83 THandler handler;
84 while (TryDequeueHandler(out handler))
84 while (TryDequeueHandler(out handler))
85 SignalHandler(handler);
85 SignalHandler(handler);
86 }
86 }
87
87
88 #endregion
88 #endregion
89
89
90 #region handlers managment
90 #region handlers managment
91
91
92 protected void AddHandler(THandler handler) {
92 protected void AddHandler(THandler handler) {
93
93
94 if (IsResolved) {
94 if (IsResolved) {
95 // the promise is in the resolved state, just invoke the handler
95 // the promise is in the resolved state, just invoke the handler
96 SignalHandler(handler);
96 SignalHandler(handler);
97 } else {
97 } else {
98 EnqueueHandler(handler);
98 EnqueueHandler(handler);
99
99
100 if (IsResolved && TryDequeueHandler(out handler))
100 if (IsResolved && TryDequeueHandler(out handler))
101 // if the promise have been resolved while we was adding the handler to the queue
101 // if the promise have been resolved while we was adding the handler to the queue
102 // we can't guarantee that someone is still processing it
102 // we can't guarantee that someone is still processing it
103 // therefore we need to fetch a handler from the queue and execute it
103 // therefore we need to fetch a handler from the queue and execute it
104 // note that fetched handler may be not the one that we have added
104 // note that fetched handler may be not the one that we have added
105 // even we can fetch no handlers at all :)
105 // even we can fetch no handlers at all :)
106 SignalHandler(handler);
106 SignalHandler(handler);
107 }
107 }
108
108
109 }
109 }
110
110
111 void EnqueueHandler(THandler handler) {
111 void EnqueueHandler(THandler handler) {
112 if (Interlocked.CompareExchange(ref m_handler, handler, null) != null) {
112 if (Interlocked.CompareExchange(ref m_handler, handler, null) != null) {
113 if (m_extraHandlers == null)
113 if (m_extraHandlers == null)
114 // compare-exchange will protect from loosing already created queue
114 // compare-exchange will protect from loosing already created queue
115 Interlocked.CompareExchange(ref m_extraHandlers, new SimpleAsyncQueue<THandler>(), null);
115 Interlocked.CompareExchange(ref m_extraHandlers, new SimpleAsyncQueue<THandler>(), null);
116 m_extraHandlers.Enqueue(handler);
116 m_extraHandlers.Enqueue(handler);
117 }
117 }
118 }
118 }
119
119
120 bool TryDequeueHandler(out THandler handler) {
120 bool TryDequeueHandler(out THandler handler) {
121 handler = Interlocked.Exchange(ref m_handler, null);
121 handler = Interlocked.Exchange(ref m_handler, null);
122 if (handler != null)
122 if (handler != null)
123 return true;
123 return true;
124 return m_extraHandlers != null && m_extraHandlers.TryDequeue(out handler);
124 return m_extraHandlers != null && m_extraHandlers.TryDequeue(out handler);
125 }
125 }
126
126
127 #endregion
127 #endregion
128 }
128 }
129 }
129 }
130
130
@@ -1,73 +1,127
1 using System;
1 using System;
2 using System.Collections.Generic;
2 using System.Collections.Generic;
3 using System.Diagnostics;
3 using System.Diagnostics;
4 using System.Linq;
4 using System.Linq;
5 using System.Text;
5 using System.Text;
6 using System.Threading.Tasks;
6 using System.Threading.Tasks;
7
7
8 namespace Implab.Diagnostics {
8 namespace Implab.Diagnostics {
9 public static class Trace<T> {
9 public static class Trace<T> {
10
10
11 readonly static TraceSource _traceSource = new TraceSource(typeof(T).Name);
11 readonly static TraceSource _traceSource = new TraceSource(typeof(T).Name);
12
12
13 public static TraceSource TraceSource {
13 public static TraceSource TraceSource {
14 get { return _traceSource; }
14 get { return _traceSource; }
15 }
15 }
16
16
17 /// <summary>
17 /// <summary>
18 /// Starts the logical operation nested to the current operation nested to the current one.
18 /// Starts the logical operation nested to the current operation nested to the current one.
19 /// </summary>
19 /// </summary>
20 [Conditional("TRACE")]
20 [Conditional("TRACE")]
21 public static void StartLogicalOperation() {
21 public static void StartLogicalOperation() {
22 Trace.CorrelationManager.StartLogicalOperation();
22 Trace.CorrelationManager.StartLogicalOperation();
23
23
24 }
24 }
25
25
26 /// <summary>
26 /// <summary>
27 /// Starts the logical operation with the specified name, this name is usefull in logs.
27 /// Starts the logical operation with the specified name, this name is usefull in logs.
28 /// </summary>
28 /// </summary>
29 /// <param name="name">Name.</param>
29 /// <param name="name">Name.</param>
30 [Conditional("TRACE")]
30 [Conditional("TRACE")]
31 public static void StartLogicalOperation(string name) {
31 public static void StartLogicalOperation(string name) {
32 Trace.CorrelationManager.StartLogicalOperation();
32 Trace.CorrelationManager.StartLogicalOperation(name);
33 }
33 }
34
34
35 /// <summary>
35 /// <summary>
36 /// Ends the logical operation and restores the previous one.
36 /// Ends the logical operation and restores the previous one.
37 /// </summary>
37 /// </summary>
38 [Conditional("TRACE")]
38 [Conditional("TRACE")]
39 public static void StopLogicalOperation() {
39 public static void StopLogicalOperation() {
40 Trace.CorrelationManager.StopLogicalOperation();
40 Trace.CorrelationManager.StopLogicalOperation();
41 }
41 }
42
42
43 /// <summary>
43 /// <summary>
44 /// Writes an informational message.
44 /// Writes an informational message.
45 /// </summary>
45 /// </summary>
46 /// <param name="format">Format.</param>
46 /// <param name="format">Format.</param>
47 /// <param name="arguments">Arguments.</param>
47 /// <param name="arguments">Arguments.</param>
48 [Conditional("TRACE")]
48 [Conditional("TRACE")]
49 public static void Log(string format, params object[] arguments) {
49 public static void Log(string format, params object[] arguments) {
50 TraceSource.TraceEvent(TraceEventType.Information, 1, format, arguments);
50 TraceSource.TraceEvent(TraceEventType.Information, 0, format, arguments);
51 }
51 }
52
52
53 /// <summary>
53 /// <summary>
54 /// Writes a warning message.
54 /// Writes a warning message.
55 /// </summary>
55 /// </summary>
56 /// <param name="format">Format.</param>
56 /// <param name="format">Format.</param>
57 /// <param name="arguments">Arguments.</param>
57 /// <param name="arguments">Arguments.</param>
58 [Conditional("TRACE")]
58 [Conditional("TRACE")]
59 public static void Warn(string format, params object[] arguments) {
59 public static void Warn(string format, params object[] arguments) {
60 TraceSource.TraceEvent(TraceEventType.Warning, 1, format, arguments);
60 TraceSource.TraceEvent(TraceEventType.Warning, 0, format, arguments);
61 }
61 }
62
62
63 [Conditional("TRACE")]
63 [Conditional("TRACE")]
64 public static void Error(string format, params object[] arguments) {
64 public static void Error(string format, params object[] arguments) {
65 TraceSource.TraceEvent(TraceEventType.Error, 1, format, arguments);
65 TraceSource.TraceEvent(TraceEventType.Error, 0, format, arguments);
66 }
66 }
67
67
68 [Conditional("TRACE")]
68 [Conditional("TRACE")]
69 public static void Error(Exception err) {
69 public static void Error(Exception err) {
70 TraceSource.TraceData(TraceEventType.Error, 1, err);
70 TraceSource.TraceData(TraceEventType.Error, 0, err);
71 }
72
73 /// <summary>
74 /// This method save the current activity, and transfers to the specified activity,
75 /// emits <see cref="TraceEventType.Start"/> and returns a scope of the new
76 /// activity.
77 /// </summary>
78 /// <param name="activityName">The name of the new activity/</param>
79 /// <param name="activityId">The identifier of the activity to which
80 /// the control will be transferred</param>
81 /// <returns>A scope of the new activity, dispose it to transfer
82 /// the control back to the original activity.</returns>
83 public static ActivityScope TransferActivity(string activityName, Guid activityId) {
84 var prev = Trace.CorrelationManager.ActivityId;
85
86 TraceSource.TraceTransfer(0, "Transfer", activityId);
87 Trace.CorrelationManager.ActivityId = activityId;
88 TraceSource.TraceEvent(TraceEventType.Start, 0, activityName);
89
90 return new ActivityScope(TraceSource, prev, 0, activityName);
91 }
92
93 /// <summary>
94 /// Emits <see cref="TraceEventType.Start"/> and returns a scope of the
95 /// activity.
96 /// </summary>
97 /// <param name="activityName">The name of the activity to start</param>
98 /// <returns>A scope of the new activity, dispose it to emit
99 /// <see cref="TraceEventType.Stop"/> for the current activity.</returns>
100 public static ActivityScope StartActivity(string activityName) {
101 if (Trace.CorrelationManager.ActivityId == Guid.Empty)
102 Trace.CorrelationManager.ActivityId = Guid.NewGuid();
103
104 var prev = Trace.CorrelationManager.ActivityId;
105
106 TraceSource.TraceEvent(TraceEventType.Start, 0, activityName);
107 return new ActivityScope(TraceSource, prev, 0, activityName);
108 }
109
110 /// <summary>
111 /// Creates new <see cref="LogicalOperation(string)"/> and calls
112 /// to <see cref="CorrelationManager.StartLogicalOperation(object)"/>
113 /// passing the created operation as identity. Calls
114 /// <see cref="TraceSource.TraceData(TraceEventType, int, object)"/>
115 /// to notify listeners on operation start.
116 /// </summary>
117 /// <param name="name">The name of the logical operation.</param>
118 /// <returns>Logical operation scope, disposing it will stop
119 /// logical operation and notify trace listeners.</returns>
120 public static LogicalOperationScope LogicalOperation(string name) {
121 var operation = new LogicalOperation(name);
122 TraceSource.TraceData(TraceEventType.Information, TraceEventCodes.StartLogicalOperation, operation);
123 Trace.CorrelationManager.StartLogicalOperation(operation);
124 return new LogicalOperationScope(TraceSource, operation);
71 }
125 }
72 }
126 }
73 }
127 }
@@ -1,8 +1,18
1 <Project Sdk="Microsoft.NET.Sdk">
1 <Project Sdk="Microsoft.NET.Sdk">
2
2
3 <PropertyGroup>
3 <PropertyGroup>
4 <Authors>Sergey Smirnov</Authors>
5 <Title>Implab library</Title>
6 <Description>Provides some helper clesses like XML serialization helpers, JSON XML reader,
7 JSON pull-parser, ECMA-style promises, lightweight synchonization routines Signal
8 and SharedLock, Trace helpers on top of System.Diagnostics, ObjectPool etc.
9 </Description>
10 <Copyright>2012-2018 Sergey Smirnov</Copyright>
11 <LicenseUrl>https://opensource.org/licenses/BSD-2-Clause</LicenseUrl>
12 <ProjectUrl>https://implab.org</ProjectUrl>
13 <RepositoryUrl>https://hg.implab.org/pub/ImplabNet/</RepositoryUrl>
4 <TargetFrameworks>netstandard2.0;net45</TargetFrameworks>
14 <TargetFrameworks>netstandard2.0;net45</TargetFrameworks>
5 <FrameworkPathOverride Condition="'$(TargetFramework)'=='net45' and '$(OSTYPE)'=='linux'">/usr/lib/mono/4.5/</FrameworkPathOverride>
15 <FrameworkPathOverride Condition="'$(TargetFramework)'=='net45' and '$(OSTYPE)'=='linux'">/usr/lib/mono/4.5/</FrameworkPathOverride>
6 </PropertyGroup>
16 </PropertyGroup>
7
17
8 </Project>
18 </Project>
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 3
Under Review
author

Auto status change to "Under Review"

Approved
author

ok, latest stable version should be in default

You need to be logged in to leave comments. Login now