Skip to content

Commit 9f9dd52

Browse files
authored
Add memory usage to contexts (#2133)
1 parent f172c4d commit 9f9dd52

File tree

4 files changed

+192
-5
lines changed

4 files changed

+192
-5
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Add memory usage to contexts ([#2133](https://github.com/getsentry/sentry-dart/pull/2133))
8+
- Only for Linux/Windows applications, as iOS/Android/macOS use native SDKs
9+
510
### Fixes
611

712
- App starts hanging for 30s ([#2140](https://github.com/getsentry/sentry-dart/pull/2140))

dart/lib/src/event_processor/enricher/io_enricher_event_processor.dart

+19-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:io';
22

33
import '../../../sentry.dart';
44
import 'enricher_event_processor.dart';
5+
import 'io_platform_memory.dart';
56

67
EnricherEventProcessor enricherEventProcessor(SentryOptions options) {
78
return IoEnricherEventProcessor(options);
@@ -17,25 +18,29 @@ class IoEnricherEventProcessor implements EnricherEventProcessor {
1718

1819
@override
1920
SentryEvent? apply(SentryEvent event, Hint hint) {
21+
// Amend app with current memory usage, as this is not available on native.
22+
final app = _getApp(event.contexts.app);
23+
2024
// If there's a native integration available, it probably has better
2125
// information available than Flutter.
2226

23-
final os = _options.platformChecker.hasNativeIntegration
24-
? null
25-
: _getOperatingSystem(event.contexts.operatingSystem);
26-
2727
final device = _options.platformChecker.hasNativeIntegration
2828
? null
2929
: _getDevice(event.contexts.device);
3030

31+
final os = _options.platformChecker.hasNativeIntegration
32+
? null
33+
: _getOperatingSystem(event.contexts.operatingSystem);
34+
3135
final culture = _options.platformChecker.hasNativeIntegration
3236
? null
3337
: _getSentryCulture(event.contexts.culture);
3438

3539
final contexts = event.contexts.copyWith(
36-
operatingSystem: os,
3740
device: device,
41+
operatingSystem: os,
3842
runtimes: _getRuntimes(event.contexts.runtimes),
43+
app: app,
3944
culture: culture,
4045
);
4146

@@ -97,9 +102,18 @@ class IoEnricherEventProcessor implements EnricherEventProcessor {
97102
}
98103

99104
SentryDevice _getDevice(SentryDevice? device) {
105+
final platformMemory = PlatformMemory(_options);
100106
return (device ?? SentryDevice()).copyWith(
101107
name: device?.name ?? Platform.localHostname,
102108
processorCount: device?.processorCount ?? Platform.numberOfProcessors,
109+
memorySize: device?.memorySize ?? platformMemory.getTotalPhysicalMemory(),
110+
freeMemory: device?.freeMemory ?? platformMemory.getFreePhysicalMemory(),
111+
);
112+
}
113+
114+
SentryApp _getApp(SentryApp? app) {
115+
return (app ?? SentryApp()).copyWith(
116+
appMemory: app?.appMemory ?? ProcessInfo.currentRss,
103117
);
104118
}
105119

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import 'dart:io';
2+
3+
import '../../protocol.dart';
4+
import '../../sentry_options.dart';
5+
6+
// Get total & free platform memory (in bytes) for linux and windows operating systems.
7+
// Source: https://github.com/onepub-dev/system_info/blob/8a9bf6b8eb7c86a09b3c3df4bf6d7fa5a6b50732/lib/src/platform/memory.dart
8+
class PlatformMemory {
9+
PlatformMemory(this.options);
10+
11+
final SentryOptions options;
12+
13+
int? getTotalPhysicalMemory() {
14+
if (options.platformChecker.platform.isLinux) {
15+
return _getLinuxMemInfoValue('MemTotal');
16+
} else if (options.platformChecker.platform.isWindows) {
17+
return _getWindowsWmicValue('ComputerSystem', 'TotalPhysicalMemory');
18+
} else {
19+
return null;
20+
}
21+
}
22+
23+
int? getFreePhysicalMemory() {
24+
if (options.platformChecker.platform.isLinux) {
25+
return _getLinuxMemInfoValue('MemFree');
26+
} else if (options.platformChecker.platform.isWindows) {
27+
return _getWindowsWmicValue('OS', 'FreePhysicalMemory');
28+
} else {
29+
return null;
30+
}
31+
}
32+
33+
int? _getWindowsWmicValue(String section, String key) {
34+
final os = _wmicGetValueAsMap(section, [key]);
35+
final totalPhysicalMemoryValue = os?[key];
36+
if (totalPhysicalMemoryValue == null) {
37+
return null;
38+
}
39+
final size = int.tryParse(totalPhysicalMemoryValue);
40+
if (size == null) {
41+
return null;
42+
}
43+
return size;
44+
}
45+
46+
int? _getLinuxMemInfoValue(String key) {
47+
final meminfoList = _exec('cat', ['/proc/meminfo'])
48+
?.trim()
49+
.replaceAll('\r\n', '\n')
50+
.split('\n') ??
51+
[];
52+
53+
final meminfoMap = _listToMap(meminfoList, ':');
54+
final memsizeResults = meminfoMap[key]?.split(' ') ?? [];
55+
56+
if (memsizeResults.isEmpty) {
57+
return null;
58+
}
59+
final memsizeResult = memsizeResults.first;
60+
61+
final memsize = int.tryParse(memsizeResult);
62+
if (memsize == null) {
63+
return null;
64+
}
65+
return memsize;
66+
}
67+
68+
String? _exec(String executable, List<String> arguments,
69+
{bool runInShell = false}) {
70+
try {
71+
final result =
72+
Process.runSync(executable, arguments, runInShell: runInShell);
73+
if (result.exitCode == 0) {
74+
return result.stdout.toString();
75+
}
76+
} catch (e) {
77+
options.logger(SentryLevel.warning, "Failed to run process: $e");
78+
}
79+
return null;
80+
}
81+
82+
Map<String, String>? _wmicGetValueAsMap(String section, List<String> fields) {
83+
final arguments = <String>[section];
84+
arguments
85+
..add('get')
86+
..addAll(fields.join(', ').split(' '))
87+
..add('/VALUE');
88+
89+
final list =
90+
_exec('wmic', arguments)?.trim().replaceAll('\r\n', '\n').split('\n') ??
91+
[];
92+
93+
return _listToMap(list, '=');
94+
}
95+
96+
Map<String, String> _listToMap(List<String> list, String separator) {
97+
final map = <String, String>{};
98+
for (final string in list) {
99+
final index = string.indexOf(separator);
100+
if (index != -1) {
101+
final key = string.substring(0, index).trim();
102+
final value = string.substring(index + 1).trim();
103+
map[key] = value;
104+
}
105+
}
106+
return map;
107+
}
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
@TestOn('vm')
2+
library dart_test;
3+
4+
import 'dart:io';
5+
6+
import 'package:sentry/sentry.dart';
7+
import 'package:sentry/src/event_processor/enricher/io_platform_memory.dart';
8+
import 'package:test/test.dart';
9+
10+
void main() {
11+
late Fixture fixture;
12+
13+
setUp(() {
14+
fixture = Fixture();
15+
});
16+
17+
test('total physical memory', () {
18+
final sut = fixture.getSut();
19+
final totalPhysicalMemory = sut.getTotalPhysicalMemory();
20+
21+
switch (Platform.operatingSystem) {
22+
case 'linux':
23+
expect(totalPhysicalMemory, isNotNull);
24+
expect(totalPhysicalMemory! > 0, true);
25+
break;
26+
case 'windows':
27+
expect(totalPhysicalMemory, isNotNull);
28+
expect(totalPhysicalMemory! > 0, true);
29+
break;
30+
default:
31+
expect(totalPhysicalMemory, isNull);
32+
}
33+
});
34+
35+
test('free physical memory', () {
36+
final sut = fixture.getSut();
37+
final freePhysicalMemory = sut.getTotalPhysicalMemory();
38+
39+
switch (Platform.operatingSystem) {
40+
case 'linux':
41+
expect(freePhysicalMemory, isNotNull);
42+
expect(freePhysicalMemory! > 0, true);
43+
break;
44+
case 'windows':
45+
expect(freePhysicalMemory, isNotNull);
46+
expect(freePhysicalMemory! > 0, true);
47+
break;
48+
default:
49+
expect(freePhysicalMemory, isNull);
50+
}
51+
});
52+
}
53+
54+
class Fixture {
55+
var options = SentryOptions();
56+
57+
PlatformMemory getSut() {
58+
return PlatformMemory(options);
59+
}
60+
}

0 commit comments

Comments
 (0)