Skip to content
This repository was archived by the owner on Nov 4, 2024. It is now read-only.

Commit fe75f13

Browse files
author
Mikhail Arkhipov
authored
Expose event for 'analysis done' (microsoft#1149)
* First cut * Move telemetry to server * Cleanup * Better track time spent * Rename field
1 parent ba4f04d commit fe75f13

File tree

8 files changed

+208
-38
lines changed

8 files changed

+208
-38
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright(c) Microsoft Corporation
2+
// All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the License); you may not use
5+
// this file except in compliance with the License. You may obtain a copy of the
6+
// License at http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
9+
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
10+
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
11+
// MERCHANTABILITY OR NON-INFRINGEMENT.
12+
//
13+
// See the Apache Version 2.0 License for specific language governing
14+
// permissions and limitations under the License.
15+
16+
using System.Collections.Generic;
17+
using System.Diagnostics;
18+
using System.Linq;
19+
20+
namespace Microsoft.Python.Analysis.Analyzer {
21+
internal static class ActivityTracker {
22+
private static readonly Dictionary<string, AnalysisState> _modules = new Dictionary<string, AnalysisState>();
23+
private static readonly object _lock = new object();
24+
private static bool _tracking;
25+
private static Stopwatch _sw;
26+
27+
private struct AnalysisState {
28+
public int Count;
29+
public bool IsComplete;
30+
}
31+
32+
public static void OnEnqueueModule(string path) {
33+
if (string.IsNullOrEmpty(path)) {
34+
return;
35+
}
36+
37+
lock (_lock) {
38+
if (!_modules.TryGetValue(path, out var st)) {
39+
_modules[path] = default;
40+
} else {
41+
st.IsComplete = false;
42+
}
43+
}
44+
}
45+
46+
public static void OnModuleAnalysisComplete(string path) {
47+
lock (_lock) {
48+
if (_modules.TryGetValue(path, out var st)) {
49+
st.Count++;
50+
st.IsComplete = true;
51+
}
52+
}
53+
}
54+
55+
public static bool IsAnalysisComplete {
56+
get {
57+
lock (_lock) {
58+
return _modules.All(m => m.Value.IsComplete);
59+
}
60+
}
61+
}
62+
63+
64+
public static void StartTracking() {
65+
lock (_lock) {
66+
if (!_tracking) {
67+
_tracking = true;
68+
_modules.Clear();
69+
_sw = Stopwatch.StartNew();
70+
}
71+
}
72+
}
73+
74+
public static void EndTracking() {
75+
lock (_lock) {
76+
if (_tracking) {
77+
_sw?.Stop();
78+
_tracking = false;
79+
}
80+
}
81+
}
82+
83+
public static int ModuleCount {
84+
get {
85+
lock (_lock) {
86+
return _modules.Count;
87+
}
88+
}
89+
}
90+
public static double MillisecondsElapsed => _sw?.Elapsed.TotalMilliseconds ?? 0;
91+
}
92+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright(c) Microsoft Corporation
2+
// All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the License); you may not use
5+
// this file except in compliance with the License. You may obtain a copy of the
6+
// License at http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
9+
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
10+
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
11+
// MERCHANTABILITY OR NON-INFRINGEMENT.
12+
//
13+
// See the Apache Version 2.0 License for specific language governing
14+
// permissions and limitations under the License.
15+
16+
using System;
17+
18+
namespace Microsoft.Python.Analysis.Analyzer {
19+
public sealed class AnalysisCompleteEventArgs : EventArgs {
20+
public int ModuleCount { get; }
21+
public double MillisecondsElapsed { get; }
22+
23+
public AnalysisCompleteEventArgs(int moduleCount, double msElapsed) {
24+
ModuleCount = moduleCount;
25+
MillisecondsElapsed = msElapsed;
26+
}
27+
}
28+
}

src/Analysis/Ast/Impl/Analyzer/Definitions/IPythonAnalyzer.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// See the Apache Version 2.0 License for specific language governing
1414
// permissions and limitations under the License.
1515

16+
using System;
1617
using System.Collections.Generic;
1718
using System.Threading;
1819
using System.Threading.Tasks;
@@ -64,5 +65,10 @@ public interface IPythonAnalyzer {
6465
/// Returns list of currently loaded modules.
6566
/// </summary>
6667
IReadOnlyList<IPythonModule> LoadedModules { get; }
68+
69+
/// <summary>
70+
/// Fires when analysis is complete.
71+
/// </summary>
72+
event EventHandler<AnalysisCompleteEventArgs> AnalysisComplete;
6773
}
6874
}

src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,22 @@ public void ResetAnalyzer() {
194194
}
195195
}
196196

197-
public IReadOnlyList<IPythonModule> LoadedModules
198-
=> _analysisEntries.Values.ExcludeDefault().Select(v => v.Module).ExcludeDefault().ToArray();
197+
public IReadOnlyList<IPythonModule> LoadedModules {
198+
get {
199+
lock (_syncObj) {
200+
return _analysisEntries.Values.ExcludeDefault().Select(v => v.Module).ExcludeDefault().ToArray();
201+
}
202+
}
203+
}
204+
205+
public event EventHandler<AnalysisCompleteEventArgs> AnalysisComplete;
206+
207+
internal void RaiseAnalysisComplete(int moduleCount, double msElapsed)
208+
=> AnalysisComplete?.Invoke(this, new AnalysisCompleteEventArgs(moduleCount, msElapsed));
199209

200210
private void AnalyzeDocument(AnalysisModuleKey key, PythonAnalyzerEntry entry, ImmutableArray<AnalysisModuleKey> dependencies) {
201211
_analysisCompleteEvent.Reset();
212+
ActivityTracker.StartTracking();
202213
_log?.Log(TraceEventType.Verbose, $"Analysis of {entry.Module.Name}({entry.Module.ModuleType}) queued");
203214

204215
var graphVersion = _dependencyResolver.ChangeValue(key, entry, entry.IsUserModule, dependencies);

src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerSession.cs

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -137,20 +137,25 @@ private async Task StartAsync() {
137137
stopWatch.Stop();
138138

139139
bool isCanceled;
140+
bool isFinal;
140141
lock (_syncObj) {
141142
isCanceled = _isCanceled;
142143
_state = State.Completed;
144+
isFinal = _walker.MissingKeys.Count == 0 && !isCanceled && remaining == 0;
143145
_walker = null;
144146
}
145147

146148
if (!isCanceled) {
147149
_progress.ReportRemaining(remaining);
150+
if(isFinal) {
151+
ActivityTracker.EndTracking();
152+
(_analyzer as PythonAnalyzer)?.RaiseAnalysisComplete(ActivityTracker.ModuleCount, ActivityTracker.MillisecondsElapsed);
153+
_log?.Log(TraceEventType.Verbose, $"Analysis complete: {ActivityTracker.ModuleCount} modules in { ActivityTracker.MillisecondsElapsed} ms.");
154+
}
148155
}
149156
}
150157

151158
var elapsed = stopWatch.Elapsed.TotalMilliseconds;
152-
153-
SendTelemetry(_telemetry, elapsed, originalRemaining, remaining, Version);
154159
LogResults(_log, elapsed, originalRemaining, remaining, Version);
155160
ForceGCIfNeeded(originalRemaining, remaining);
156161
}
@@ -162,38 +167,6 @@ private static void ForceGCIfNeeded(int originalRemaining, int remaining) {
162167
}
163168
}
164169

165-
private static void SendTelemetry(ITelemetryService telemetry, double elapsed, int originalRemaining, int remaining, int version) {
166-
if (telemetry == null) {
167-
return;
168-
}
169-
170-
if (remaining != 0 || originalRemaining < 100) {
171-
return;
172-
}
173-
174-
double privateMB;
175-
double peakPagedMB;
176-
double workingMB;
177-
178-
using (var proc = Process.GetCurrentProcess()) {
179-
privateMB = proc.PrivateMemorySize64 / 1e+6;
180-
peakPagedMB = proc.PeakPagedMemorySize64 / 1e+6;
181-
workingMB = proc.WorkingSet64 / 1e+6;
182-
}
183-
184-
var e = new TelemetryEvent {
185-
EventName = "python_language_server/analysis_complete", // TODO: Move this common prefix into Core.
186-
};
187-
188-
e.Measurements["privateMB"] = privateMB;
189-
e.Measurements["peakPagedMB"] = peakPagedMB;
190-
e.Measurements["workingMB"] = workingMB;
191-
e.Measurements["elapsedMs"] = elapsed;
192-
e.Measurements["entries"] = originalRemaining;
193-
e.Measurements["version"] = version;
194-
195-
telemetry.SendTelemetryAsync(e).DoNotWait();
196-
}
197170

198171
private static void LogResults(ILogger logger, double elapsed, int originalRemaining, int remaining, int version) {
199172
if (logger == null) {
@@ -226,6 +199,8 @@ private async Task<int> AnalyzeAffectedEntriesAsync(Stopwatch stopWatch) {
226199
continue;
227200
}
228201

202+
ActivityTracker.OnEnqueueModule(node.Value.Module.FilePath);
203+
229204
if (Interlocked.Increment(ref _runningTasks) >= _maxTaskRunning || _walker.Remaining == 1) {
230205
Analyze(node, null, stopWatch);
231206
} else {
@@ -241,7 +216,7 @@ private async Task<int> AnalyzeAffectedEntriesAsync(Stopwatch stopWatch) {
241216

242217
if (_walker.MissingKeys.Count == 0 || _walker.MissingKeys.All(k => k.IsTypeshed)) {
243218
Interlocked.Exchange(ref _runningTasks, 0);
244-
219+
245220
if (!isCanceled) {
246221
_analysisCompleteEvent.Set();
247222
}
@@ -279,6 +254,7 @@ private void Analyze(IDependencyChainNode<PythonAnalyzerEntry> node, AsyncCountd
279254
var startTime = stopWatch.Elapsed;
280255
AnalyzeEntry(entry, module, ast, _walker.Version);
281256
node.Commit();
257+
ActivityTracker.OnModuleAnalysisComplete(node.Value.Module.FilePath);
282258

283259
LogCompleted(module, stopWatch, startTime);
284260
} catch (OperationCanceledException oce) {

src/Analysis/Ast/Impl/Dependencies/IDependencyChainWalker.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ internal interface IDependencyChainWalker<TKey, TValue> {
2323
ImmutableArray<TValue> AffectedValues { get; }
2424
int Version { get; }
2525
int Remaining { get; }
26-
2726
Task<IDependencyChainNode<TValue>> GetNextAsync(CancellationToken cancellationToken);
2827
}
2928
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright(c) Microsoft Corporation
2+
// All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the License); you may not use
5+
// this file except in compliance with the License. You may obtain a copy of the
6+
// License at http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
9+
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
10+
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
11+
// MERCHANTABILITY OR NON-INFRINGEMENT.
12+
//
13+
// See the Apache Version 2.0 License for specific language governing
14+
// permissions and limitations under the License.
15+
16+
using System.Diagnostics;
17+
using Microsoft.Python.Analysis.Analyzer;
18+
using Microsoft.Python.Core;
19+
using Microsoft.Python.Core.Services;
20+
21+
namespace Microsoft.Python.LanguageServer.Implementation {
22+
public sealed partial class Server {
23+
private void OnAnalysisComplete(object sender, AnalysisCompleteEventArgs e) {
24+
if (e.MillisecondsElapsed < 500) {
25+
return;
26+
}
27+
var telemetry = _services.GetService<ITelemetryService>();
28+
if (telemetry == null) {
29+
return;
30+
}
31+
32+
double privateMB;
33+
double peakPagedMB;
34+
double workingMB;
35+
36+
using (var proc = Process.GetCurrentProcess()) {
37+
privateMB = proc.PrivateMemorySize64 / 1e+6;
38+
peakPagedMB = proc.PeakPagedMemorySize64 / 1e+6;
39+
workingMB = proc.WorkingSet64 / 1e+6;
40+
}
41+
42+
var te = new TelemetryEvent {
43+
EventName = "python_language_server/analysis_complete", // TODO: Move this common prefix into Core.
44+
};
45+
46+
te.Measurements["privateMB"] = privateMB;
47+
te.Measurements["peakPagedMB"] = peakPagedMB;
48+
te.Measurements["workingMB"] = workingMB;
49+
te.Measurements["elapsedMs"] = e.MillisecondsElapsed;
50+
te.Measurements["moduleCount"] = e.ModuleCount;
51+
52+
telemetry.SendTelemetryAsync(te).DoNotWait();
53+
}
54+
}
55+
}

src/LanguageServer/Impl/Implementation/Server.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ public async Task<InitializeResult> InitializeAsync(InitializeParams @params, Ca
107107
var analyzer = new PythonAnalyzer(_services, cacheFolderPath);
108108
_services.AddService(analyzer);
109109

110+
analyzer.AnalysisComplete += OnAnalysisComplete;
111+
_disposableBag.Add(() => analyzer.AnalysisComplete -= OnAnalysisComplete);
112+
110113
_services.AddService(new RunningDocumentTable(_services));
111114
_rdt = _services.GetService<IRunningDocumentTable>();
112115

0 commit comments

Comments
 (0)