Skip to content

Commit 24087e1

Browse files
authored
Limit writes to session file (#243)
* Refactor to use modified dt for _lastPing * Fix tests + misc dartdoc fixes * Bump version * Create `sessionFile` in constructor * Simplify `lastPingDateTime` * Revert filename + pass session id to initializer * Use nullable field for `_sessionId` * Format fix * Change to wip + use `else` block for timestamp
1 parent fca993e commit 24087e1

File tree

7 files changed

+61
-80
lines changed

7 files changed

+61
-80
lines changed

pkgs/unified_analytics/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 5.8.6-wip
2+
3+
- Refactored session handler class to use the last modified timestamp as the last ping value
4+
15
## 5.8.5
26

37
- Fix late initialization error for `Analytics.userProperty` [bug](https://github.com/dart-lang/tools/issues/238)

pkgs/unified_analytics/lib/src/analytics.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,8 +454,8 @@ class AnalyticsImpl implements Analytics {
454454
errorHandler: _errorHandler,
455455
);
456456

457-
// Initialize the session handler with the session_id and last_ping
458-
// variables by parsing the json file
457+
// Initialize the session handler with the session_id
458+
// by parsing the json file
459459
_sessionHandler.initialize(telemetryEnabled);
460460
}
461461

pkgs/unified_analytics/lib/src/constants.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ const int kLogFileLength = 2500;
8282
const String kLogFileName = 'dart-flutter-telemetry.log';
8383

8484
/// The current version of the package, should be in line with pubspec version.
85-
const String kPackageVersion = '5.8.5';
85+
const String kPackageVersion = '5.8.6-wip';
8686

8787
/// The minimum length for a session.
8888
const int kSessionDurationMinutes = 30;

pkgs/unified_analytics/lib/src/initializer.dart

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:convert';
6-
75
import 'package:clock/clock.dart';
86
import 'package:file/file.dart';
97
import 'package:path/path.dart' as p;
@@ -75,17 +73,19 @@ class Initializer {
7573
logFile.createSync(recursive: true);
7674
}
7775

78-
/// Creates the session json file which will contain
79-
/// the current session id along with the timestamp for
80-
/// the last ping which will be used to increment the session
81-
/// if current timestamp is greater than the session window.
82-
static void createSessionFile({required File sessionFile}) {
83-
final now = clock.now();
76+
/// Creates the session file which will contain
77+
/// the current session id which is the current timestamp.
78+
///
79+
/// [sessionIdOverride] can be provided as an override, otherwise it
80+
/// will use the current timestamp from [Clock.now].
81+
static void createSessionFile({
82+
required File sessionFile,
83+
DateTime? sessionIdOverride,
84+
}) {
85+
final now = sessionIdOverride ?? clock.now();
8486
sessionFile.createSync(recursive: true);
85-
sessionFile.writeAsStringSync(jsonEncode(<String, int>{
86-
'session_id': now.millisecondsSinceEpoch,
87-
'last_ping': now.millisecondsSinceEpoch,
88-
}));
87+
sessionFile
88+
.writeAsStringSync('{"session_id": ${now.millisecondsSinceEpoch}}');
8989
}
9090

9191
/// This will check that there is a client ID populated in

pkgs/unified_analytics/lib/src/session.dart

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ class Session {
1919
final File sessionFile;
2020
final ErrorHandler _errorHandler;
2121

22-
late int _sessionId;
23-
late int _lastPing;
22+
int? _sessionId;
2423

2524
Session({
2625
required this.homeDirectory,
@@ -31,7 +30,7 @@ class Session {
3130
_errorHandler = errorHandler;
3231

3332
/// This will use the data parsed from the
34-
/// session json file in the dart-tool directory
33+
/// session file in the dart-tool directory
3534
/// to get the session id if the last ping was within
3635
/// [kSessionDurationMinutes].
3736
///
@@ -40,30 +39,27 @@ class Session {
4039
///
4140
/// Note, the file will always be updated when calling this method
4241
/// because the last ping variable will always need to be persisted.
43-
int getSessionId() {
42+
int? getSessionId() {
4443
_refreshSessionData();
4544
final now = clock.now();
4645

4746
// Convert the epoch time from the last ping into datetime and check if we
4847
// are within the kSessionDurationMinutes.
49-
final lastPingDateTime = DateTime.fromMillisecondsSinceEpoch(_lastPing);
48+
final lastPingDateTime = sessionFile.lastModifiedSync();
5049
if (now.difference(lastPingDateTime).inMinutes > kSessionDurationMinutes) {
51-
// In this case, we will need to change both the session id
52-
// and the last ping value
50+
// Update the session file with the latest session id
5351
_sessionId = now.millisecondsSinceEpoch;
52+
sessionFile.writeAsStringSync('{"session_id": $_sessionId}');
53+
} else {
54+
// Update the last modified timestamp with the current timestamp so that
55+
// we can use it for the next _lastPing calculation
56+
sessionFile.setLastModifiedSync(now);
5457
}
5558

56-
// Update the last ping to reflect session activity continuing
57-
_lastPing = now.millisecondsSinceEpoch;
58-
59-
// Rewrite the session object back to the file to persist
60-
// for future events
61-
sessionFile.writeAsStringSync(toJson());
62-
6359
return _sessionId;
6460
}
6561

66-
/// Preps the [Session] class with the data found in the session json file.
62+
/// Preps the [Session] class with the data found in the session file.
6763
///
6864
/// We must check if telemetry is enabled to refresh the session data
6965
/// because the refresh method will write to the session file and for
@@ -73,15 +69,9 @@ class Session {
7369
if (telemetryEnabled) _refreshSessionData();
7470
}
7571

76-
/// Return a json formatted representation of the class.
77-
String toJson() => jsonEncode(<String, int>{
78-
'session_id': _sessionId,
79-
'last_ping': _lastPing,
80-
});
81-
8272
/// This will go to the session file within the dart-tool
83-
/// directory and fetch the latest data from the json to update
84-
/// the class's variables. If the json file is malformed, a new
73+
/// directory and fetch the latest data from the session file to update
74+
/// the class's variables. If the session file is malformed, a new
8575
/// session file will be recreated.
8676
///
8777
/// This allows the session data in this class to always be up
@@ -94,13 +84,18 @@ class Session {
9484
final sessionObj =
9585
jsonDecode(sessionFileContents) as Map<String, Object?>;
9686
_sessionId = sessionObj['session_id'] as int;
97-
_lastPing = sessionObj['last_ping'] as int;
9887
}
9988

10089
try {
90+
// Failing to parse the contents will result in the current timestamp
91+
// being used as the session id and will get used to recreate the file
10192
parseContents();
10293
} on FormatException catch (err) {
103-
Initializer.createSessionFile(sessionFile: sessionFile);
94+
final now = clock.now();
95+
Initializer.createSessionFile(
96+
sessionFile: sessionFile,
97+
sessionIdOverride: now,
98+
);
10499

105100
_errorHandler.log(Event.analyticsException(
106101
workflow: 'Session._refreshSessionData',
@@ -109,11 +104,13 @@ class Session {
109104
));
110105

111106
// Fallback to setting the session id as the current time
112-
final now = clock.now();
113107
_sessionId = now.millisecondsSinceEpoch;
114-
_lastPing = now.millisecondsSinceEpoch;
115108
} on FileSystemException catch (err) {
116-
Initializer.createSessionFile(sessionFile: sessionFile);
109+
final now = clock.now();
110+
Initializer.createSessionFile(
111+
sessionFile: sessionFile,
112+
sessionIdOverride: now,
113+
);
117114

118115
_errorHandler.log(Event.analyticsException(
119116
workflow: 'Session._refreshSessionData',
@@ -122,9 +119,7 @@ class Session {
122119
));
123120

124121
// Fallback to setting the session id as the current time
125-
final now = clock.now();
126122
_sessionId = now.millisecondsSinceEpoch;
127-
_lastPing = now.millisecondsSinceEpoch;
128123
}
129124
}
130125
}

pkgs/unified_analytics/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: >-
44
to Google Analytics.
55
# When updating this, keep the version consistent with the changelog and the
66
# value in lib/src/constants.dart.
7-
version: 5.8.5
7+
version: 5.8.6-wip
88
repository: https://github.com/dart-lang/tools/tree/main/pkgs/unified_analytics
99

1010
environment:

pkgs/unified_analytics/test/unified_analytics_test.dart

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ void main() {
132132

133133
test('Resetting session file when data is malformed', () {
134134
// Purposefully write content to the session file that
135-
// can't be decoded as json
135+
// can't be decoded as an integer
136136
sessionFile.writeAsStringSync('contents');
137137

138138
// Define the initial time to start
@@ -143,8 +143,7 @@ void main() {
143143
final timestamp = clock.now().millisecondsSinceEpoch.toString();
144144
expect(sessionFile.readAsStringSync(), 'contents');
145145
analytics.userProperty.preparePayload();
146-
expect(sessionFile.readAsStringSync(),
147-
'{"session_id":$timestamp,"last_ping":$timestamp}');
146+
expect(sessionFile.readAsStringSync(), '{"session_id": $timestamp}');
148147

149148
// Attempting to fetch the session id when malformed should also
150149
// send an error event while parsing
@@ -158,9 +157,9 @@ void main() {
158157

159158
test('Handles malformed session file on startup', () {
160159
// Ensure that we are able to send an error message on startup if
161-
// we encounter an error while parsing the contents of the json file
160+
// we encounter an error while parsing the contents of the session file
162161
// for session data
163-
sessionFile.writeAsStringSync('contents');
162+
sessionFile.writeAsStringSync('not a valid session id');
164163

165164
analytics = Analytics.test(
166165
tool: initialTool,
@@ -185,7 +184,7 @@ void main() {
185184
expect(errorEvent!.eventData['workflow'], 'Session._refreshSessionData');
186185
expect(errorEvent.eventData['error'], 'FormatException');
187186
expect(errorEvent.eventData['description'],
188-
'message: Unexpected character\nsource: contents');
187+
'message: Unexpected character\nsource: not a valid session id');
189188
});
190189

191190
test('Resetting session file when file is removed', () {
@@ -200,8 +199,7 @@ void main() {
200199
final timestamp = clock.now().millisecondsSinceEpoch.toString();
201200
expect(sessionFile.existsSync(), false);
202201
analytics.userProperty.preparePayload();
203-
expect(sessionFile.readAsStringSync(),
204-
'{"session_id":$timestamp,"last_ping":$timestamp}');
202+
expect(sessionFile.readAsStringSync(), '{"session_id": $timestamp}');
205203

206204
// Attempting to fetch the session id when malformed should also
207205
// send an error event while parsing
@@ -701,14 +699,10 @@ ${initialTool.label}=$dateStamp,$toolsMessageVersion
701699
);
702700
secondAnalytics.clientShowedMessage();
703701

704-
// Read the contents of the session file
705-
final sessionFileContents = sessionFile.readAsStringSync();
706-
final sessionObj =
707-
jsonDecode(sessionFileContents) as Map<String, Object?>;
708-
709702
expect(secondAnalytics.userPropertyMap['session_id']?['value'],
710703
start.millisecondsSinceEpoch);
711-
expect(sessionObj['last_ping'], start.millisecondsSinceEpoch);
704+
expect(sessionFile.lastModifiedSync().millisecondsSinceEpoch,
705+
start.millisecondsSinceEpoch);
712706
});
713707

714708
// Add time to the start time that is less than the duration
@@ -739,17 +733,13 @@ ${initialTool.label}=$dateStamp,$toolsMessageVersion
739733
// no events will be sent
740734
thirdAnalytics.send(testEvent);
741735

742-
// Read the contents of the session file
743-
final sessionFileContents = sessionFile.readAsStringSync();
744-
final sessionObj =
745-
jsonDecode(sessionFileContents) as Map<String, Object?>;
746-
747736
expect(thirdAnalytics.userPropertyMap['session_id']?['value'],
748737
start.millisecondsSinceEpoch,
749738
reason: 'The session id should not have changed since it was made '
750739
'within the duration');
751-
expect(sessionObj['last_ping'], end.millisecondsSinceEpoch,
752-
reason: 'The last_ping value should have been updated');
740+
expect(sessionFile.lastModifiedSync().millisecondsSinceEpoch,
741+
end.millisecondsSinceEpoch,
742+
reason: 'The last modified value should have been updated');
753743
});
754744
});
755745

@@ -782,14 +772,10 @@ ${initialTool.label}=$dateStamp,$toolsMessageVersion
782772
);
783773
secondAnalytics.clientShowedMessage();
784774

785-
// Read the contents of the session file
786-
final sessionFileContents = sessionFile.readAsStringSync();
787-
final sessionObj =
788-
jsonDecode(sessionFileContents) as Map<String, Object?>;
789-
790775
expect(secondAnalytics.userPropertyMap['session_id']?['value'],
791776
start.millisecondsSinceEpoch);
792-
expect(sessionObj['last_ping'], start.millisecondsSinceEpoch);
777+
expect(sessionFile.lastModifiedSync().millisecondsSinceEpoch,
778+
start.millisecondsSinceEpoch);
793779

794780
secondAnalytics.send(testEvent);
795781
});
@@ -822,17 +808,13 @@ ${initialTool.label}=$dateStamp,$toolsMessageVersion
822808
// no events will be sent
823809
thirdAnalytics.send(testEvent);
824810

825-
// Read the contents of the session file
826-
final sessionFileContents = sessionFile.readAsStringSync();
827-
final sessionObj =
828-
jsonDecode(sessionFileContents) as Map<String, Object?>;
829-
830811
expect(thirdAnalytics.userPropertyMap['session_id']?['value'],
831812
end.millisecondsSinceEpoch,
832813
reason: 'The session id should have changed since it was made '
833814
'outside the duration');
834-
expect(sessionObj['last_ping'], end.millisecondsSinceEpoch,
835-
reason: 'The last_ping value should have been updated');
815+
expect(sessionFile.lastModifiedSync().millisecondsSinceEpoch,
816+
end.millisecondsSinceEpoch,
817+
reason: 'The last modified value should have been updated');
836818
});
837819
});
838820

0 commit comments

Comments
 (0)