Skip to content

Commit fe4bf82

Browse files
author
Quoc Truong
committed
Improve Performance of ScriptAnalyzer by using BackgroundWorkers
1 parent c85bf65 commit fe4bf82

File tree

1 file changed

+123
-48
lines changed

1 file changed

+123
-48
lines changed

Engine/Commands/InvokeScriptAnalyzerCommand.cs

Lines changed: 123 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
using System.Text.RegularExpressions;
1414
using System;
15+
using System.ComponentModel;
1516
using System.Collections.Generic;
1617
using System.Diagnostics.CodeAnalysis;
1718
using System.Globalization;
@@ -20,6 +21,9 @@
2021
using System.Management.Automation.Language;
2122
using System.IO;
2223
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
24+
using System.Threading.Tasks;
25+
using System.Collections.Concurrent;
26+
using System.Threading;
2327

2428
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands
2529
{
@@ -267,6 +271,14 @@ private void ProcessPath(string path)
267271

268272
}
269273

274+
ConcurrentBag<DiagnosticRecord> diagnostics;
275+
ConcurrentBag<SuppressedRecord> suppressed;
276+
Dictionary<string, List<RuleSuppression>> ruleSuppressions;
277+
List<Regex> includeRegexList;
278+
List<Regex> excludeRegexList;
279+
CountdownEvent cde;
280+
ConcurrentDictionary<string, List<object>> ruleDictionary;
281+
270282
/// <summary>
271283
/// Analyzes a single script file.
272284
/// </summary>
@@ -275,15 +287,16 @@ private void AnalyzeFile(string filePath)
275287
{
276288
Token[] tokens = null;
277289
ParseError[] errors = null;
278-
List<DiagnosticRecord> diagnostics = new List<DiagnosticRecord>();
279-
List<SuppressedRecord> suppressed = new List<SuppressedRecord>();
290+
diagnostics = new ConcurrentBag<DiagnosticRecord>();
291+
suppressed = new ConcurrentBag<SuppressedRecord>();
292+
ruleDictionary = new ConcurrentDictionary<string, List<object>>();
280293

281294
// Use a List of KVP rather than dictionary, since for a script containing inline functions with same signature, keys clash
282295
List<KeyValuePair<CommandInfo, IScriptExtent>> cmdInfoTable = new List<KeyValuePair<CommandInfo, IScriptExtent>>();
283296

284297
//Check wild card input for the Include/ExcludeRules and create regex match patterns
285-
List<Regex> includeRegexList = new List<Regex>();
286-
List<Regex> excludeRegexList = new List<Regex>();
298+
includeRegexList = new List<Regex>();
299+
excludeRegexList = new List<Regex>();
287300
if (includeRule != null)
288301
{
289302
foreach (string rule in includeRule)
@@ -331,7 +344,7 @@ private void AnalyzeFile(string filePath)
331344
return;
332345
}
333346

334-
Dictionary<string, List<RuleSuppression>> ruleSuppressions = Helper.Instance.GetRuleSuppression(ast);
347+
ruleSuppressions = Helper.Instance.GetRuleSuppression(ast);
335348

336349
foreach (List<RuleSuppression> ruleSuppressionsList in ruleSuppressions.Values)
337350
{
@@ -360,44 +373,24 @@ private void AnalyzeFile(string filePath)
360373

361374
if (ScriptAnalyzer.Instance.ScriptRules != null)
362375
{
363-
foreach (IScriptRule scriptRule in ScriptAnalyzer.Instance.ScriptRules)
376+
cde = new CountdownEvent(ScriptAnalyzer.Instance.ScriptRules.Count());
377+
378+
foreach (var scriptRule in ScriptAnalyzer.Instance.ScriptRules)
364379
{
365-
bool includeRegexMatch = false;
366-
bool excludeRegexMatch = false;
367-
foreach (Regex include in includeRegexList)
368-
{
369-
if (include.IsMatch(scriptRule.GetName()))
370-
{
371-
includeRegexMatch = true;
372-
break;
373-
}
374-
}
380+
BackgroundWorker bg = new BackgroundWorker();
381+
bg.DoWork += bg_DoWork;
382+
bg.RunWorkerAsync(new object[] { scriptRule });
383+
}
375384

376-
foreach (Regex exclude in excludeRegexList)
377-
{
378-
if (exclude.IsMatch(scriptRule.GetName()))
379-
{
380-
excludeRegexMatch = true;
381-
break;
382-
}
383-
}
385+
cde.Wait();
384386

385-
if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch))
387+
foreach (var rule in ruleDictionary.Keys)
388+
{
389+
List<object> verboseOrErrors = ruleDictionary[rule];
390+
WriteVerbose(verboseOrErrors[0] as string);
391+
if (verboseOrErrors.Count == 2)
386392
{
387-
WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName()));
388-
389-
// Ensure that any unhandled errors from Rules are converted to non-terminating errors
390-
// We want the Engine to continue functioning even if one or more Rules throws an exception
391-
try
392-
{
393-
var records = Helper.Instance.SuppressRule(scriptRule.GetName(), ruleSuppressions, scriptRule.AnalyzeScript(ast, filePath).ToList());
394-
diagnostics.AddRange(records.Item2);
395-
suppressed.AddRange(records.Item1);
396-
}
397-
catch (Exception scriptRuleException)
398-
{
399-
WriteError(new ErrorRecord(scriptRuleException, Strings.RuleErrorMessage, ErrorCategory.InvalidOperation, filePath));
400-
}
393+
WriteError(verboseOrErrors[1] as ErrorRecord);
401394
}
402395
}
403396
}
@@ -437,8 +430,14 @@ private void AnalyzeFile(string filePath)
437430
try
438431
{
439432
var records = Helper.Instance.SuppressRule(tokenRule.GetName(), ruleSuppressions, tokenRule.AnalyzeTokens(tokens, filePath).ToList());
440-
diagnostics.AddRange(records.Item2);
441-
suppressed.AddRange(records.Item1);
433+
foreach (var record in records.Item2)
434+
{
435+
diagnostics.Add(record);
436+
}
437+
foreach (var suppressedRec in records.Item1)
438+
{
439+
suppressed.Add(suppressedRec);
440+
}
442441
}
443442
catch (Exception tokenRuleException)
444443
{
@@ -489,8 +488,14 @@ private void AnalyzeFile(string filePath)
489488
try
490489
{
491490
var records = Helper.Instance.SuppressRule(dscResourceRule.GetName(), ruleSuppressions, dscResourceRule.AnalyzeDSCClass(ast, filePath).ToList());
492-
diagnostics.AddRange(records.Item2);
493-
suppressed.AddRange(records.Item1);
491+
foreach (var record in records.Item2)
492+
{
493+
diagnostics.Add(record);
494+
}
495+
foreach (var suppressedRec in records.Item1)
496+
{
497+
suppressed.Add(suppressedRec);
498+
}
494499
}
495500
catch (Exception dscResourceRuleException)
496501
{
@@ -532,8 +537,14 @@ private void AnalyzeFile(string filePath)
532537
try
533538
{
534539
var records = Helper.Instance.SuppressRule(dscResourceRule.GetName(), ruleSuppressions, dscResourceRule.AnalyzeDSCResource(ast, filePath).ToList());
535-
diagnostics.AddRange(records.Item2);
536-
suppressed.AddRange(records.Item1);
540+
foreach (var record in records.Item2)
541+
{
542+
diagnostics.Add(record);
543+
}
544+
foreach (var suppressedRec in records.Item1)
545+
{
546+
suppressed.Add(suppressedRec);
547+
}
537548
}
538549
catch (Exception dscResourceRuleException)
539550
{
@@ -573,15 +584,20 @@ private void AnalyzeFile(string filePath)
573584
}
574585
}
575586

576-
diagnostics.AddRange(ScriptAnalyzer.Instance.GetExternalRecord(ast, tokens, exRules.ToArray(), this, fileName));
587+
foreach (var record in ScriptAnalyzer.Instance.GetExternalRecord(ast, tokens, exRules.ToArray(), this, fileName))
588+
{
589+
diagnostics.Add(record);
590+
}
577591
}
578592

579593
#endregion
580594

595+
IEnumerable<DiagnosticRecord> diagnosticsList = diagnostics;
596+
581597
if (severity != null)
582598
{
583599
var diagSeverity = severity.Select(item => Enum.Parse(typeof(DiagnosticSeverity), item, true));
584-
diagnostics = diagnostics.Where(item => diagSeverity.Contains(item.Severity)).ToList();
600+
diagnosticsList = diagnostics.Where(item => diagSeverity.Contains(item.Severity));
585601
}
586602

587603
//Output through loggers
@@ -596,14 +612,73 @@ private void AnalyzeFile(string filePath)
596612
}
597613
else
598614
{
599-
foreach (DiagnosticRecord diagnostic in diagnostics)
615+
foreach (DiagnosticRecord diagnostic in diagnosticsList)
600616
{
601617
logger.LogObject(diagnostic, this);
602618
}
603619
}
604620
}
605621
}
606622

623+
void bg_DoWork(object sender, DoWorkEventArgs e)
624+
{
625+
bool includeRegexMatch = false;
626+
bool excludeRegexMatch = false;
627+
628+
object[] parameters = e.Argument as object[];
629+
630+
IScriptRule scriptRule = parameters[0] as IScriptRule;
631+
632+
foreach (Regex include in includeRegexList)
633+
{
634+
if (include.IsMatch(scriptRule.GetName()))
635+
{
636+
includeRegexMatch = true;
637+
break;
638+
}
639+
}
640+
641+
foreach (Regex exclude in excludeRegexList)
642+
{
643+
if (exclude.IsMatch(scriptRule.GetName()))
644+
{
645+
excludeRegexMatch = true;
646+
break;
647+
}
648+
}
649+
650+
List<object> result = new List<object>();
651+
652+
if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch))
653+
{
654+
//WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName()));
655+
result.Add(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName()));
656+
657+
// Ensure that any unhandled errors from Rules are converted to non-terminating errors
658+
// We want the Engine to continue functioning even if one or more Rules throws an exception
659+
try
660+
{
661+
var records = Helper.Instance.SuppressRule(scriptRule.GetName(), ruleSuppressions, scriptRule.AnalyzeScript(ast, ast.Extent.File).ToList());
662+
foreach (var record in records.Item2)
663+
{
664+
diagnostics.Add(record);
665+
}
666+
foreach (var suppressedRec in records.Item1)
667+
{
668+
suppressed.Add(suppressedRec);
669+
}
670+
}
671+
catch (Exception scriptRuleException)
672+
{
673+
result.Add(new ErrorRecord(scriptRuleException, Strings.RuleErrorMessage, ErrorCategory.InvalidOperation, ast.Extent.File));
674+
}
675+
}
676+
677+
ruleDictionary[scriptRule.GetName()] = result;
678+
679+
cde.Signal();
680+
}
681+
607682
#endregion
608683
}
609684
}

0 commit comments

Comments
 (0)