Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
BuildThreadMonitor.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.Diagnostics;
6 using System.Linq;
7 using System.Threading;
8 
9 using SiliconStudio.Core.Diagnostics;
10 using SiliconStudio.Core.MicroThreading;
11 using System.ServiceModel;
12 
13 namespace SiliconStudio.BuildEngine
14 {
15  internal class BuildThreadMonitor
16  {
17  /// <summary>
18  /// This class stores informations relative to a build step, used to build data to send to the clients. It is immutable.
19  /// </summary>
20  private class BuildStepInfo
21  {
22  public readonly BuildStep BuildStep;
23  public readonly long ExecutionId;
24  public readonly string Description;
25  public readonly TimestampLocalLogger Logger;
26  public bool HasBeenSend { get; private set; }
27 
28  public BuildStepInfo(BuildStep buildStep, long executionId, string description, TimestampLocalLogger logger)
29  {
30  BuildStep = buildStep;
31  ExecutionId = executionId;
32  Description = description;
33  Logger = logger;
34  HasBeenSend = false;
35  }
36 
37  public void BuildStepSent()
38  {
39  HasBeenSend = true;
40  }
41 
42  public override string ToString()
43  {
44  return "[" + ExecutionId + "] " + BuildStep;
45  }
46  }
47 
48  private readonly Dictionary<int, List<TimeInterval>> threadExecutionIntervals = new Dictionary<int, List<TimeInterval>>();
49 
50  private readonly List<BuildStepInfo> buildStepInfos = new List<BuildStepInfo>();
51  private readonly List<BuildStepInfo> buildStepInfosToSend = new List<BuildStepInfo>();
52  private readonly List<long> buildStepResultsToSend = new List<long>();
53 
54  private readonly List<MicrothreadNotification> microthreadNotifications = new List<MicrothreadNotification>();
55 
56  private readonly Guid builderId;
57  private readonly string monitorPipeName;
58 
59  private readonly Stopwatch stopWatch = new Stopwatch();
60 
61  private DateTime startTime;
62 
63  // Datetime's ticks are hardware-independent and 100ns long
64  private static readonly double TickFactor = 10000000.0 / Stopwatch.Frequency;
65 
66  private IBuildMonitorRemote buildMonitorRemote;
67 
68  private bool running;
69 
70  private Thread monitorThread;
71 
72  internal BuildThreadMonitor(Scheduler scheduler, Guid builderId, string monitorPipeName = Builder.MonitorPipeName)
73  {
74  this.monitorPipeName = monitorPipeName;
75  this.builderId = builderId;
76 
77  scheduler.MicroThreadStarted += MicroThreadStarted;
78  scheduler.MicroThreadEnded += MicroThreadEnded;
79  scheduler.MicroThreadCallbackStart += MicroThreadCallbackStart;
80  scheduler.MicroThreadCallbackEnd += MicroThreadCallbackEnd;
81  stopWatch.Start();
82  }
83 
84  internal void RegisterThread(int threadId)
85  {
86  lock (threadExecutionIntervals)
87  {
88  threadExecutionIntervals.Add(threadId, new List<TimeInterval>());
89  }
90  }
91 
92  internal void RegisterBuildStep(BuildStep buildStep, TimestampLocalLogger logger)
93  {
94  lock (buildStepInfosToSend)
95  {
96  buildStepInfosToSend.Add(new BuildStepInfo(buildStep, buildStep.ExecutionId, buildStep.Description, logger));
97  }
98  }
99 
100  public void Start()
101  {
102  startTime = DateTime.Now;
103  running = true;
104 
105  monitorThread = new Thread(SafeAction.Wrap(() =>
106  {
107  if (TryConnectMonitor())
108  buildMonitorRemote.StartBuild(builderId, startTime);
109 
110  int delay = 300;
111  while (running)
112  {
113  Thread.Sleep(delay);
114  delay = SendThreadUpdate() ? 300 : 1000;
115  }
116  SendThreadUpdate();
117 
118  if (TryConnectMonitor())
119  buildMonitorRemote.EndBuild(builderId, DateTime.Now);
120 
121  try
122  {
123  // ReSharper disable SuspiciousTypeConversion.Global
124  var communicationObj = buildMonitorRemote as ICommunicationObject;
125  // ReSharper restore SuspiciousTypeConversion.Global
126  if (communicationObj != null)
127  communicationObj.Close();
128  }
129  // We don't know the layer to close under the client channel so it might throw potentially any exception.
130  // Let's ignore them all because at this step we're just cleaning up things.
131  // ReSharper disable EmptyGeneralCatchClause
132  catch
133  // ReSharper restore EmptyGeneralCatchClause
134  { }
135  }))
136  { IsBackground = true, Name = "Monitor Thread" };
137 
138  monitorThread.Start();
139  }
140 
141  public void Finish()
142  {
143  running = false;
144  }
145 
146  public void Join()
147  {
148  if (monitorThread != null)
149  {
150  monitorThread.Join();
151  monitorThread = null;
152  }
153  }
154 
155  private bool TryConnectMonitor()
156  {
157  if (buildMonitorRemote == null)
158  {
159  try
160  {
161  var namedPipeBinding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None) { SendTimeout = TimeSpan.FromSeconds(300.0) };
162  buildMonitorRemote = ChannelFactory<IBuildMonitorRemote>.CreateChannel(namedPipeBinding, new EndpointAddress(monitorPipeName));
163  buildMonitorRemote.Ping();
164  }
165  catch (EndpointNotFoundException)
166  {
167  buildMonitorRemote = null;
168  }
169  }
170 
171  return buildMonitorRemote != null;
172  }
173 
174  private bool SendThreadUpdate()
175  {
176  if (!TryConnectMonitor())
177  return false;
178 
179  // Update local info
180  var localMicroThreadNotifications = new List<MicrothreadNotification>();
181  var localBuildStepResultsToSend = new List<long>();
182 
183  lock (microthreadNotifications)
184  {
185  localMicroThreadNotifications.AddRange(microthreadNotifications);
186  microthreadNotifications.Clear();
187  }
188 
189  lock (buildStepInfosToSend)
190  {
191  buildStepInfos.AddRange(buildStepInfosToSend);
192  buildStepInfosToSend.Clear();
193  }
194 
195  lock (buildStepResultsToSend)
196  {
197  localBuildStepResultsToSend.AddRange(buildStepResultsToSend);
198  buildStepResultsToSend.Clear();
199  }
200 
201  try
202  {
203  // Sending BuildStep view model
204  foreach (var buildStepInfo in buildStepInfos.Where(x => !x.HasBeenSend))
205  {
206  buildMonitorRemote.SendBuildStepInfo(builderId, buildStepInfo.ExecutionId, buildStepInfo.Description, startTime);
207  buildStepInfo.BuildStepSent();
208  }
209 
210  buildMonitorRemote.SendMicrothreadEvents(builderId, startTime, DateTime.Now, localMicroThreadNotifications);
211 
212  // Sending log message
213  foreach (var buildStepInfo in buildStepInfos)
214  {
215  if (buildStepInfo.Logger != null)
216  {
217  TimestampLocalLogger.Message[] messages = null;
218  lock (buildStepInfo.Logger)
219  {
220  if (buildStepInfo.Logger.Messages.Count > 0)
221  {
222  messages = buildStepInfo.Logger.Messages.ToArray();
223  buildStepInfo.Logger.Messages.Clear();
224  }
225  }
226  if (messages != null)
227  {
228  try
229  {
230  var serializableMessages = (messages.Select(x => new SerializableTimestampLogMessage(x))).ToList();
231  buildMonitorRemote.SendCommandLog(builderId, startTime, buildStepInfo.ExecutionId, serializableMessages);
232  }
233  catch (Exception)
234  {
235  lock (buildStepInfo.Logger)
236  {
237  buildStepInfo.Logger.Messages.InsertRange(0, messages);
238  }
239  throw;
240  }
241  }
242  }
243  }
244 
245  // Sending BuildStep results
246  for (int i = localBuildStepResultsToSend.Count - 1; i >= 0; --i)
247  {
248  long microthreadId = localBuildStepResultsToSend[i];
249  BuildStepInfo stepInfo = buildStepInfos.SingleOrDefault(x => x.ExecutionId == microthreadId);
250  if (stepInfo != null && stepInfo.BuildStep != null)
251  {
252  buildMonitorRemote.SendBuildStepResult(builderId, startTime, microthreadId, stepInfo.BuildStep.Status);
253  localBuildStepResultsToSend.RemoveAt(i);
254  }
255  }
256  }
257  catch (Exception)
258  {
259  lock (microthreadNotifications)
260  {
261  microthreadNotifications.AddRange(localMicroThreadNotifications);
262  }
263 
264  buildMonitorRemote = null;
265  }
266  finally
267  {
268  if (localBuildStepResultsToSend.Count > 0)
269  {
270  lock (buildStepResultsToSend)
271  {
272  buildStepResultsToSend.AddRange(localBuildStepResultsToSend);
273  }
274  }
275  }
276  return buildMonitorRemote != null;
277  }
278 
279  private void MicroThreadStarted(object sender, SchedulerThreadEventArgs e)
280  {
281  // Not useful anymore? Let's do nothing for the moment
282  }
283 
284  private void MicroThreadEnded(object sender, SchedulerThreadEventArgs e)
285  {
286  lock (buildStepResultsToSend)
287  {
288  buildStepResultsToSend.Add(e.MicroThread.Id);
289  }
290  }
291 
292  private void MicroThreadCallbackStart(object sender, SchedulerThreadEventArgs e)
293  {
294  TimeInterval timeInterval;
295  int intervalCount;
296  lock (threadExecutionIntervals)
297  {
298  List<TimeInterval> intervals = threadExecutionIntervals[e.ThreadId];
299  if (intervals.Count > 0 && !intervals.Last().HasEnded)
300  throw new InvalidOperationException("Starting a new microthread on a thread still running another microthread.");
301 
302  timeInterval = new TimeInterval(GetTicksFromStopwatch());
303  intervals.Add(timeInterval);
304  intervalCount = intervals.Count;
305  }
306 
307  // Rely on intervals.Count, so must be called after intervals.Add!
308  long jobId = GetMicrothreadJobIdFromThreadInfo(e.ThreadId, intervalCount);
309  var jobInfo = new MicrothreadNotification(e.ThreadId, e.MicroThread.Id, jobId, timeInterval.StartTime, MicrothreadNotification.NotificationType.JobStarted);
310 
311  lock (microthreadNotifications)
312  {
313  microthreadNotifications.Add(jobInfo);
314  }
315  }
316 
317  private void MicroThreadCallbackEnd(object sender, SchedulerThreadEventArgs e)
318  {
319  long endTime = GetTicksFromStopwatch();
320  int intervalCount;
321  lock (threadExecutionIntervals)
322  {
323  List<TimeInterval> intervals = threadExecutionIntervals[e.ThreadId];
324  intervals.Last().End(endTime);
325  intervalCount = intervals.Count;
326  }
327  long jobId = GetMicrothreadJobIdFromThreadInfo(e.ThreadId, intervalCount);
328  var jobInfo = new MicrothreadNotification(e.ThreadId, e.MicroThread.Id, jobId, endTime, MicrothreadNotification.NotificationType.JobEnded);
329 
330  lock (microthreadNotifications)
331  {
332  microthreadNotifications.Add(jobInfo);
333  }
334  }
335 
336  private long GetMicrothreadJobIdFromThreadInfo(int threadId, int threadIntervalCount)
337  {
338  unchecked
339  {
340  long result = threadIntervalCount;
341  result += ((long)threadId) << (sizeof(int) * 8);
342  return result;
343  }
344  }
345 
346  private long GetTicksFromStopwatch()
347  {
348  return (long)(stopWatch.ElapsedTicks * TickFactor);
349  }
350  }
351 }
352 
A structure describing a log message associated with a timestamp.
static ThreadStart Wrap(ThreadStart action, [CallerFilePath] string sourceFilePath="", [CallerMemberName] string memberName="", [CallerLineNumber] int sourceLineNumber=0)
Definition: SafeAction.cs:13
Base implementation for ILogger.
Definition: Logger.cs:10
MicroThread MicroThread
Gets or sets the MicroThread this event concerns.
long Id
Gets the id of this MicroThread.
Definition: MicroThread.cs:106
Scheduler that manage a group of cooperating MicroThread.
Definition: Scheduler.cs:20
A logger that stores messages locally with their timestamp, useful for internal log scenarios...
int ThreadId
Gets or sets the System.Threading.Thread.ManagedThreadId active when this event happened.
Provides data for the Scheduler.MicroThreadStarted, Scheduler.MicroThreadEnded, Scheduler.MicroThreadCallbackStart and Scheduler.MicroThreadCallbackEnd events.