add memory usage to the diagnostics page

BUG=
R=brianwilkerson@google.com, scheglov@google.com

Review-Url: https://codereview.chromium.org/2927783002 .
This commit is contained in:
Devon Carew 2017-06-07 11:28:33 -07:00
parent c510737b17
commit 79c2668d2b
7 changed files with 214 additions and 56 deletions

View file

@ -107,7 +107,7 @@ class AnalysisServer {
* The version of the analysis server. The value should be replaced
* automatically during the build.
*/
static final String VERSION = '1.18.0';
static final String VERSION = '1.18.1';
/**
* The number of milliseconds to perform operations before inserting

View file

@ -124,3 +124,11 @@ class DiagnosticDomainHandler implements RequestHandler {
return null;
}
}
class MemoryCpuSample {
final DateTime time;
final double cpuPercentage;
final int memoryKB;
MemoryCpuSample(this.time, this.cpuPercentage, this.memoryKB);
}

View file

@ -5,7 +5,6 @@
import 'dart:async';
import 'dart:io';
import 'package:analysis_server/src/channel/web_socket_channel.dart';
import 'package:analysis_server/src/socket_server.dart';
import 'package:analysis_server/src/status/diagnostics.dart';
@ -36,7 +35,7 @@ class HttpAnalysisServer {
/**
* Future that is completed with the HTTP server once it is running.
*/
Future<HttpServer> _server;
Future<HttpServer> _serverFuture;
/**
* Last PRINT_BUFFER_LENGTH lines printed.
@ -51,10 +50,10 @@ class HttpAnalysisServer {
/**
* Return the port this server is bound to.
*/
Future<int> get boundPort async => (await _server)?.port;
Future<int> get boundPort async => (await _serverFuture)?.port;
void close() {
_server.then((HttpServer server) {
_serverFuture.then((HttpServer server) {
server.close();
});
}
@ -74,17 +73,22 @@ class HttpAnalysisServer {
* Begin serving HTTP requests over the given port.
*/
Future<int> serveHttp([int initialPort]) async {
if (_server != null) {
if (_serverFuture != null) {
return boundPort;
}
try {
_server =
HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, initialPort ?? 0);
HttpServer server = await _server;
_serverFuture =
HttpServer.bind(InternetAddress.LOOPBACK_IP_V6, initialPort ?? 0);
HttpServer server = await _serverFuture;
_handleServer(server);
return server.port;
} catch (ignore) {
// If we can't bind to the specified port, don't remember the broken
// server.
_serverFuture = null;
return null;
}
}
@ -110,29 +114,24 @@ class HttpAnalysisServer {
void _handleServer(HttpServer httpServer) {
httpServer.listen((HttpRequest request) {
List<String> updateValues = request.headers[HttpHeaders.UPGRADE];
if (updateValues != null && updateValues.indexOf('websocket') >= 0) {
WebSocketTransformer.upgrade(request).then((WebSocket websocket) {
_handleWebSocket(websocket);
});
} else if (request.method == 'GET') {
if (request.method == 'GET') {
_handleGetRequest(request);
} else if (updateValues != null &&
updateValues.indexOf('websocket') >= 0) {
// We no longer support serving analysis server communications over
// WebSocket connections.
HttpResponse response = request.response;
response.statusCode = HttpStatus.NOT_FOUND;
response.headers.contentType = ContentType.TEXT;
response.write(
'WebSocket connections not supported (${request.uri.path}).');
response.close();
} else {
_returnUnknownRequest(request);
}
});
}
/**
* Handle an UPGRADE request received by the HTTP server by creating and
* running an analysis server on a [WebSocket]-based communication channel.
*/
void _handleWebSocket(WebSocket socket) {
// TODO(devoncarew): This serves the analysis server over a websocket
// connection for historical reasons (and should probably be removed).
socketServer.createAnalysisServer(new WebSocketServerChannel(
socket, socketServer.instrumentationService));
}
/**
* Return an error in response to an unrecognized request received by the HTTP
* server.
@ -140,8 +139,7 @@ class HttpAnalysisServer {
void _returnUnknownRequest(HttpRequest request) {
HttpResponse response = request.response;
response.statusCode = HttpStatus.NOT_FOUND;
response.headers.contentType =
new ContentType("text", "plain", charset: "utf-8");
response.headers.contentType = ContentType.TEXT;
response.write('Not found');
response.close();
}

View file

@ -8,12 +8,14 @@ import 'dart:io';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/domain_completion.dart';
import 'package:analysis_server/src/domain_diagnostic.dart';
import 'package:analysis_server/src/domain_execution.dart';
import 'package:analysis_server/src/plugin/plugin_manager.dart';
import 'package:analysis_server/src/server/http_server.dart';
import 'package:analysis_server/src/services/completion/completion_performance.dart';
import 'package:analysis_server/src/socket_server.dart';
import 'package:analysis_server/src/status/pages.dart';
import 'package:analysis_server/src/utilities/profiling.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/instrumentation/instrumentation.dart';
import 'package:analyzer/source/package_map_resolver.dart';
@ -139,12 +141,17 @@ class DiagnosticsSite extends Site implements AbstractGetHandler {
pages.add(new CompletionPage(this));
pages.add(new CommunicationsPage(this));
pages.add(new ContextsPage(this));
pages.add(new ExecutionDomainPage(this));
pages.add(new OverlaysPage(this));
pages.add(new ProfilePage(this));
pages.add(new ExceptionsPage(this));
pages.add(new InstrumentationPage(this));
pages.add(new OverlaysPage(this));
pages.add(new PluginsPage(this));
pages.add(new ProfilePage(this));
pages.add(new SubscriptionsPage(this));
ProcessProfiler profiler = ProcessProfiler.getProfilerForPlatform();
if (profiler != null) {
pages.add(new MemoryAndCpuPage(this, profiler));
}
pages.sort(((Page a, Page b) =>
a.title.toLowerCase().compareTo(b.title.toLowerCase())));
@ -370,23 +377,6 @@ class StatusPage extends DiagnosticPageWithNav {
buf.writeln('</div>');
h3('Server domain subscriptions');
ul(ServerService.VALUES, (item) {
if (server.serverServices.contains(item)) {
buf.write('$item (has subscriptions)');
} else {
buf.write('$item (no subscriptions)');
}
});
h3('Analysis domain subscriptions');
for (AnalysisService service in AnalysisService.VALUES) {
buf.writeln('${service.name}<br>');
ul(server.analysisServices[service] ?? [], (item) {
buf.write('$item');
});
}
List<String> lines = (site as DiagnosticsSite).lastPrintedLines;
if (lines.isNotEmpty) {
h3('Debug output');
@ -717,6 +707,32 @@ class ContextsPage extends DiagnosticPageWithNav {
}
}
class MemoryAndCpuPage extends DiagnosticPageWithNav {
final ProcessProfiler profiler;
MemoryAndCpuPage(DiagnosticsSite site, this.profiler)
: super(site, 'memory', 'Memory and CPU Usage',
description: 'Memory and CPU usage for the analysis server.');
@override
void generateContent(Map<String, String> params) {
UsageInfo usage = profiler.getProcessUsageSync(pid);
if (usage != null) {
buf.writeln(
writeOption('CPU', printPercentage(usage.cpuPercentage / 100.0)));
buf.writeln(
writeOption('Memory', '${printInteger(usage.memoryMB.round())} MB'));
} else {
p('Error retreiving the memory and cpu usage information.');
}
}
DiagnosticDomainHandler get diagnosticDomain {
return server.handlers
.firstWhere((handler) => handler is DiagnosticDomainHandler);
}
}
class OverlaysPage extends DiagnosticPageWithNav {
OverlaysPage(DiagnosticsSite site)
: super(site, 'overlays', 'Overlays',
@ -729,6 +745,8 @@ class OverlaysPage extends DiagnosticPageWithNav {
String overlayPath = params['overlay'];
if (overlayPath != null) {
p(overlayPath);
if (overlays[overlayPath] != null) {
buf.write('<pre><code>');
buf.write(overlays[overlayPath]);
@ -790,18 +808,38 @@ class PluginsPage extends DiagnosticPageWithNav {
}
}
class ExecutionDomainPage extends DiagnosticPageWithNav {
ExecutionDomainPage(DiagnosticsSite site)
: super(site, 'execution', 'Execution Domain',
description: 'Data for the analysis server\'s execution domain.');
class SubscriptionsPage extends DiagnosticPageWithNav {
SubscriptionsPage(DiagnosticsSite site)
: super(site, 'subscriptions', 'Subscriptions',
description: 'Registered subscriptions to analysis server events.');
@override
void generateContent(Map<String, String> params) {
// server domain
h3('Server domain subscriptions');
ul(ServerService.VALUES, (item) {
if (server.serverServices.contains(item)) {
buf.write('$item (has subscriptions)');
} else {
buf.write('$item (no subscriptions)');
}
});
// analysis domain
h3('Analysis domain subscriptions');
for (AnalysisService service in AnalysisService.VALUES) {
buf.writeln('${service.name}<br>');
ul(server.analysisServices[service] ?? [], (item) {
buf.write('$item');
});
}
// execution domain
ExecutionDomainHandler domain = server.handlers.firstWhere(
(handler) => handler is ExecutionDomainHandler,
orElse: () => null);
h3('Subscriptions');
h3('Execution domain');
ul(ExecutionService.VALUES, (item) {
if (domain.onFileAnalyzed != null) {
buf.write('$item (has subscriptions)');
@ -819,9 +857,8 @@ class CompletionPage extends DiagnosticPageWithNav {
@override
void generateContent(Map<String, String> params) {
CompletionDomainHandler completionDomain = server.handlers.firstWhere(
(handler) => handler is CompletionDomainHandler,
orElse: () => null);
CompletionDomainHandler completionDomain = server.handlers
.firstWhere((handler) => handler is CompletionDomainHandler);
List<CompletionPerformance> completions =
completionDomain.performanceList.items.toList();

View file

@ -0,0 +1,85 @@
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:io';
/// A class that can return memory and cpu usage information for a given
/// process.
abstract class ProcessProfiler {
ProcessProfiler._();
Future<UsageInfo> getProcessUsage(int processId);
UsageInfo getProcessUsageSync(int processId);
/// Return a [ProcessProfiler] instance suitable for the current host
/// platform. This can return `null` if we're not able to gather memory and
/// cpu information for the current platform.
static ProcessProfiler getProfilerForPlatform() {
if (Platform.isLinux || Platform.isMacOS) {
return new _PosixProcessProfiler();
}
// Not a supported platform.
return null;
}
}
class UsageInfo {
/// A number between 0.0 and 100.0 * the number of host CPUs (but typically
/// never more than slightly above 100.0).
final double cpuPercentage;
/// The process memory usage in bytes.
final int memoryKB;
UsageInfo(this.cpuPercentage, this.memoryKB);
double get memoryMB => memoryKB / 1024;
}
class _PosixProcessProfiler extends ProcessProfiler {
_PosixProcessProfiler() : super._();
@override
Future<UsageInfo> getProcessUsage(int processId) {
try {
// Execution time is typically 2-4ms.
Future<ProcessResult> future =
Process.run('ps', ['-o', '%cpu=,rss=', processId.toString()]);
return future.then((ProcessResult result) {
if (result.exitCode != 0) {
return new Future.value(null);
}
return new Future.value(_parse(result.stdout));
});
} catch (e) {
return new Future.error(e);
}
}
UsageInfo getProcessUsageSync(int processId) {
try {
// Execution time is typically 2-4ms.
ProcessResult result =
Process.runSync('ps', ['-o', '%cpu=,rss=', processId.toString()]);
return result.exitCode == 0 ? _parse(result.stdout) : null;
} catch (e) {
return null;
}
}
UsageInfo _parse(String psResults) {
try {
// " 0.0 378940"
String line = psResults.split('\n').first.trim();
List<String> values = line.split(' ');
return new UsageInfo(double.parse(values[0]), int.parse(values[1]));
} catch (e) {
return null;
}
}
}

View file

@ -0,0 +1,30 @@
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
import 'package:analysis_server/src/utilities/profiling.dart';
import 'package:test/test.dart';
main() {
group('ProcessProfiler', () {
// Skip on windows.
if (Platform.isWindows) {
return;
}
test('getProfilerForPlatform', () async {
expect(ProcessProfiler.getProfilerForPlatform(), isNotNull);
});
test('getProcessUsage', () async {
ProcessProfiler profiler = ProcessProfiler.getProfilerForPlatform();
UsageInfo info = await profiler.getProcessUsage(pid);
expect(info, isNotNull);
expect(info.cpuPercentage, greaterThanOrEqualTo(0.0));
expect(info.memoryKB, greaterThanOrEqualTo(0));
});
});
}

View file

@ -8,7 +8,7 @@
* Removed `Element.docRange`.
## 0.28.2-alpha.0
* Corresponds with the analyzer/server in the `1.20.0-dev.1.0 ` SDK.
* Corresponds with the analyzer/server in the `1.20.0-dev.1.0` SDK.
## 0.28.0-alpha.2
* Fixed PubSummaryManager linking when a listed package does not have the unlinked bundle.