mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 01:13:04 +00:00
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:
parent
c510737b17
commit
79c2668d2b
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
85
pkg/analysis_server/lib/src/utilities/profiling.dart
Normal file
85
pkg/analysis_server/lib/src/utilities/profiling.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
30
pkg/analysis_server/test/src/utilities/profiling_test.dart
Normal file
30
pkg/analysis_server/test/src/utilities/profiling_test.dart
Normal 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));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue