blob: 501a8c2de595ee4209f4ec2d3cad76afb809c7f2 [file] [log] [blame]
// Copyright (c) 2023, 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:developer';
import 'dart:io';
import 'package:path/path.dart' as path;
import '_util.dart';
import 'model.dart';
class AutoSnapshotter {
AutoSnapshotter(this.config);
final AutoSnapshottingConfig config;
bool _snapshottingIsInProgress = false;
SnapshotEvent? _previousSnapshot;
bool _noSnapshotsAnyMore = false;
Future<void> autoSnapshot() async {
if (_noSnapshotsAnyMore || _snapshottingIsInProgress) return;
_snapshottingIsInProgress = true;
await _maybeTakeSnapshot();
_snapshottingIsInProgress = false;
}
Future<void> _maybeTakeSnapshot() async {
final rss = ProcessInfo.currentRss;
if (rss < config.thresholdMb.mbToBytes) {
return;
}
// Directory size validation is heavier than rss check, so we do it after.
// We do not stop monitoring, in case user will free some space.
if (_isDirectoryOversized()) return;
final stepMb = config.increaseMb;
if (_previousSnapshot == null) {
_takeSnapshot(rss: rss);
if (stepMb == null) _noSnapshotsAnyMore = true;
return;
}
final previous = _previousSnapshot!;
if (stepMb == null) {
throw StateError(
'Autosnapshotting should be off if step is null and there is a '
'snapshot already taken',
);
}
final nextAllowedSnapshotTime =
previous.timestamp.add(config.minDelayBetweenSnapshots);
if (nextAllowedSnapshotTime.isAfter(DateTime.now())) {
return;
}
final nextThreshold = previous.rss + stepMb.mbToBytes;
if (rss >= nextThreshold) {
_takeSnapshot(rss: rss);
}
}
void _takeSnapshot({required int rss}) {
final snapshotNumber = (_previousSnapshot?.snapshotNumber ?? 0) + 1;
final snapshot = saveSnapshot(
config.directory,
rss: rss,
snapshotNumber: snapshotNumber,
);
_previousSnapshot = snapshot;
config.onSnapshot?.call(snapshot);
}
bool _isDirectoryOversized() {
final directorySize = Directory(config.directory)
.listSync(recursive: true)
.whereType<File>()
.map((f) => f.lengthSync())
.fold<int>(0, (a, b) => a + b);
return directorySize >= config.directorySizeLimitMb.mbToBytes;
}
}
/// Saves a memory heap snapshot of the current process, to the [directory].
///
/// Returns the name of the file where the snapshot was saved.
///
/// The file name contains process id, snapshot number and current RSS.
SnapshotEvent saveSnapshot(
String directory, {
required int rss,
required int snapshotNumber,
void Function(String fileName) snapshotter =
NativeRuntime.writeHeapSnapshotToFile,
}) {
final fileName = path.absolute(
path.join(directory, 'snapshot-$pid-$snapshotNumber-rss$rss.json'),
);
snapshotter(fileName);
return SnapshotEvent(fileName, snapshotNumber: snapshotNumber, rss: rss);
}