Skip to content

Commit d36adcd

Browse files
joyeecheungBridgeAR
authored andcommitted
fs: add *timeNs properties to BigInt Stats objects
- Extend the aliased buffer for stats objects to contain the entire time spec (seconds and nanoseconds) for the time values instead of calculating the milliseconds in C++ and lose precision there. - Calculate the nanosecond-precision time values in JS and expose them in BigInt Stats objects as `*timeNs`. The millisecond-precision values are now calculated from the nanosecond-precision values. PR-URL: #21387 Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Gus Caplan <me@gus.host>
1 parent 5a7154e commit d36adcd

7 files changed

Lines changed: 275 additions & 129 deletions

File tree

‎doc/api/fs.md‎

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,8 @@ A `fs.Stats` object provides information about a file.
512512
Objects returned from [`fs.stat()`][], [`fs.lstat()`][] and [`fs.fstat()`][] and
513513
their synchronous counterparts are of this type.
514514
If `bigint` in the `options` passed to those methods is true, the numeric values
515-
will be `bigint` instead of `number`.
515+
will be `bigint` instead of `number`, and the object will contain additional
516+
nanosecond-precision properties suffixed with `Ns`.
516517

517518
```console
518519
Stats {
@@ -539,7 +540,7 @@ Stats {
539540
`bigint` version:
540541

541542
```console
542-
Stats {
543+
BigIntStats {
543544
dev: 2114n,
544545
ino: 48064969n,
545546
mode: 33188n,
@@ -554,6 +555,10 @@ Stats {
554555
mtimeMs: 1318289051000n,
555556
ctimeMs: 1318289051000n,
556557
birthtimeMs: 1318289051000n,
558+
atimeNs: 1318289051000000000n,
559+
mtimeNs: 1318289051000000000n,
560+
ctimeNs: 1318289051000000000n,
561+
birthtimeNs: 1318289051000000000n,
557562
atime: Mon, 10 Oct 2011 23:24:11 GMT,
558563
mtime: Mon, 10 Oct 2011 23:24:11 GMT,
559564
ctime: Mon, 10 Oct 2011 23:24:11 GMT,
@@ -726,6 +731,54 @@ added: v8.1.0
726731
The timestamp indicating the creation time of this file expressed in
727732
milliseconds since the POSIX Epoch.
728733

734+
### stats.atimeNs
735+
<!-- YAML
736+
added: REPLACEME
737+
-->
738+
739+
* {bigint}
740+
741+
Only present when `bigint: true` is passed into the method that generates
742+
the object.
743+
The timestamp indicating the last time this file was accessed expressed in
744+
nanoseconds since the POSIX Epoch.
745+
746+
### stats.mtimeNs
747+
<!-- YAML
748+
added: REPLACEME
749+
-->
750+
751+
* {bigint}
752+
753+
Only present when `bigint: true` is passed into the method that generates
754+
the object.
755+
The timestamp indicating the last time this file was modified expressed in
756+
nanoseconds since the POSIX Epoch.
757+
758+
### stats.ctimeNs
759+
<!-- YAML
760+
added: REPLACEME
761+
-->
762+
763+
* {bigint}
764+
765+
Only present when `bigint: true` is passed into the method that generates
766+
the object.
767+
The timestamp indicating the last time the file status was changed expressed
768+
in nanoseconds since the POSIX Epoch.
769+
770+
### stats.birthtimeNs
771+
<!-- YAML
772+
added: REPLACEME
773+
-->
774+
775+
* {bigint}
776+
777+
Only present when `bigint: true` is passed into the method that generates
778+
the object.
779+
The timestamp indicating the creation time of this file expressed in
780+
nanoseconds since the POSIX Epoch.
781+
729782
### stats.atime
730783
<!-- YAML
731784
added: v0.11.13
@@ -765,8 +818,17 @@ The timestamp indicating the creation time of this file.
765818
### Stat Time Values
766819

767820
The `atimeMs`, `mtimeMs`, `ctimeMs`, `birthtimeMs` properties are
768-
[numbers][MDN-Number] that hold the corresponding times in milliseconds. Their
769-
precision is platform specific. `atime`, `mtime`, `ctime`, and `birthtime` are
821+
numeric values that hold the corresponding times in milliseconds. Their
822+
precision is platform specific. When `bigint: true` is passed into the
823+
method that generates the object, the properties will be [bigints][],
824+
otherwise they will be [numbers][MDN-Number].
825+
826+
The `atimeNs`, `mtimeNs`, `ctimeNs`, `birthtimeNs` properties are
827+
[bigints][] that hold the corresponding times in nanoseconds. They are
828+
only present when `bigint: true` is passed into the method that generates
829+
the object. Their precision is platform specific.
830+
831+
`atime`, `mtime`, `ctime`, and `birthtime` are
770832
[`Date`][MDN-Date] object alternate representations of the various times. The
771833
`Date` and number values are not connected. Assigning a new number value, or
772834
mutating the `Date` value, will not be reflected in the corresponding alternate
@@ -4976,6 +5038,7 @@ the file contents.
49765038
[`net.Socket`]: net.html#net_class_net_socket
49775039
[`stat()`]: fs.html#fs_fs_stat_path_options_callback
49785040
[`util.promisify()`]: util.html#util_util_promisify_original
5041+
[bigints]: https://tc39.github.io/proposal-bigint
49795042
[Caveats]: #fs_caveats
49805043
[Common System Errors]: errors.html#errors_common_system_errors
49815044
[FS Constants]: #fs_fs_constants_1

‎lib/internal/fs/utils.js‎

Lines changed: 108 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const{ Reflect }=primordials;
3+
const{Object,Reflect }=primordials;
44

55
const{ Buffer, kMaxLength }=require('buffer');
66
const{
@@ -16,7 +16,8 @@ const {
1616
}=require('internal/errors');
1717
const{
1818
isUint8Array,
19-
isDate
19+
isDate,
20+
isBigUint64Array
2021
}=require('internal/util/types');
2122
const{ once }=require('internal/util');
2223
const{ toPathIfFileURL }=require('internal/url');
@@ -230,27 +231,9 @@ function preprocessSymlinkDestination(path, type, linkPath) {
230231
}
231232
}
232233

233-
functiondateFromNumeric(num){
234-
returnnewDate(Number(num)+0.5);
235-
}
236-
237234
// Constructor for file stats.
238-
functionStats(
239-
dev,
240-
mode,
241-
nlink,
242-
uid,
243-
gid,
244-
rdev,
245-
blksize,
246-
ino,
247-
size,
248-
blocks,
249-
atim_msec,
250-
mtim_msec,
251-
ctim_msec,
252-
birthtim_msec
253-
){
235+
functionStatsBase(dev,mode,nlink,uid,gid,rdev,blksize,
236+
ino,size,blocks){
254237
this.dev=dev;
255238
this.mode=mode;
256239
this.nlink=nlink;
@@ -261,63 +244,132 @@ function Stats(
261244
this.ino=ino;
262245
this.size=size;
263246
this.blocks=blocks;
264-
this.atimeMs=atim_msec;
265-
this.mtimeMs=mtim_msec;
266-
this.ctimeMs=ctim_msec;
267-
this.birthtimeMs=birthtim_msec;
268-
this.atime=dateFromNumeric(atim_msec);
269-
this.mtime=dateFromNumeric(mtim_msec);
270-
this.ctime=dateFromNumeric(ctim_msec);
271-
this.birthtime=dateFromNumeric(birthtim_msec);
272247
}
273248

274-
Stats.prototype._checkModeProperty=function(property){
275-
if(isWindows&&(property===S_IFIFO||property===S_IFBLK||
276-
property===S_IFSOCK)){
277-
returnfalse;// Some types are not available on Windows
278-
}
279-
if(typeofthis.mode==='bigint'){// eslint-disable-line valid-typeof
280-
return(this.mode&BigInt(S_IFMT))===BigInt(property);
281-
}
282-
return(this.mode&S_IFMT)===property;
283-
};
284-
285-
Stats.prototype.isDirectory=function(){
249+
StatsBase.prototype.isDirectory=function(){
286250
returnthis._checkModeProperty(S_IFDIR);
287251
};
288252

289-
Stats.prototype.isFile=function(){
253+
StatsBase.prototype.isFile=function(){
290254
returnthis._checkModeProperty(S_IFREG);
291255
};
292256

293-
Stats.prototype.isBlockDevice=function(){
257+
StatsBase.prototype.isBlockDevice=function(){
294258
returnthis._checkModeProperty(S_IFBLK);
295259
};
296260

297-
Stats.prototype.isCharacterDevice=function(){
261+
StatsBase.prototype.isCharacterDevice=function(){
298262
returnthis._checkModeProperty(S_IFCHR);
299263
};
300264

301-
Stats.prototype.isSymbolicLink=function(){
265+
StatsBase.prototype.isSymbolicLink=function(){
302266
returnthis._checkModeProperty(S_IFLNK);
303267
};
304268

305-
Stats.prototype.isFIFO=function(){
269+
StatsBase.prototype.isFIFO=function(){
306270
returnthis._checkModeProperty(S_IFIFO);
307271
};
308272

309-
Stats.prototype.isSocket=function(){
273+
StatsBase.prototype.isSocket=function(){
310274
returnthis._checkModeProperty(S_IFSOCK);
311275
};
312276

277+
constkNsPerMsBigInt=10n**6n;
278+
constkNsPerSecBigInt=10n**9n;
279+
constkMsPerSec=10**3;
280+
constkNsPerMs=10**6;
281+
functionmsFromTimeSpec(sec,nsec){
282+
returnsec*kMsPerSec+nsec/kNsPerMs;
283+
}
284+
285+
functionnsFromTimeSpecBigInt(sec,nsec){
286+
returnsec*kNsPerSecBigInt+nsec;
287+
}
288+
289+
functiondateFromMs(ms){
290+
returnnewDate(Number(ms)+0.5);
291+
}
292+
293+
functionBigIntStats(dev,mode,nlink,uid,gid,rdev,blksize,
294+
ino,size,blocks,
295+
atimeNs,mtimeNs,ctimeNs,birthtimeNs){
296+
StatsBase.call(this,dev,mode,nlink,uid,gid,rdev,blksize,
297+
ino,size,blocks);
298+
299+
this.atimeMs=atimeNs/kNsPerMsBigInt;
300+
this.mtimeMs=mtimeNs/kNsPerMsBigInt;
301+
this.ctimeMs=ctimeNs/kNsPerMsBigInt;
302+
this.birthtimeMs=birthtimeNs/kNsPerMsBigInt;
303+
this.atimeNs=atimeNs;
304+
this.mtimeNs=mtimeNs;
305+
this.ctimeNs=ctimeNs;
306+
this.birthtimeNs=birthtimeNs;
307+
this.atime=dateFromMs(this.atimeMs);
308+
this.mtime=dateFromMs(this.mtimeMs);
309+
this.ctime=dateFromMs(this.ctimeMs);
310+
this.birthtime=dateFromMs(this.birthtimeMs);
311+
}
312+
313+
Object.setPrototypeOf(BigIntStats.prototype,StatsBase.prototype);
314+
Object.setPrototypeOf(BigIntStats,StatsBase);
315+
316+
BigIntStats.prototype._checkModeProperty=function(property){
317+
if(isWindows&&(property===S_IFIFO||property===S_IFBLK||
318+
property===S_IFSOCK)){
319+
returnfalse;// Some types are not available on Windows
320+
}
321+
return(this.mode&BigInt(S_IFMT))===BigInt(property);
322+
};
323+
324+
functionStats(dev,mode,nlink,uid,gid,rdev,blksize,
325+
ino,size,blocks,
326+
atimeMs,mtimeMs,ctimeMs,birthtimeMs){
327+
StatsBase.call(this,dev,mode,nlink,uid,gid,rdev,blksize,
328+
ino,size,blocks);
329+
this.atimeMs=atimeMs;
330+
this.mtimeMs=mtimeMs;
331+
this.ctimeMs=ctimeMs;
332+
this.birthtimeMs=birthtimeMs;
333+
this.atime=dateFromMs(atimeMs);
334+
this.mtime=dateFromMs(mtimeMs);
335+
this.ctime=dateFromMs(ctimeMs);
336+
this.birthtime=dateFromMs(birthtimeMs);
337+
}
338+
339+
Object.setPrototypeOf(Stats.prototype,StatsBase.prototype);
340+
Object.setPrototypeOf(Stats,StatsBase);
341+
342+
Stats.prototype._checkModeProperty=function(property){
343+
if(isWindows&&(property===S_IFIFO||property===S_IFBLK||
344+
property===S_IFSOCK)){
345+
returnfalse;// Some types are not available on Windows
346+
}
347+
return(this.mode&S_IFMT)===property;
348+
};
349+
313350
functiongetStatsFromBinding(stats,offset=0){
314-
returnnewStats(stats[0+offset],stats[1+offset],stats[2+offset],
315-
stats[3+offset],stats[4+offset],stats[5+offset],
316-
stats[6+offset],// blksize
317-
stats[7+offset],stats[8+offset],
318-
stats[9+offset],// blocks
319-
stats[10+offset],stats[11+offset],
320-
stats[12+offset],stats[13+offset]);
351+
if(isBigUint64Array(stats)){
352+
returnnewBigIntStats(
353+
stats[0+offset],stats[1+offset],stats[2+offset],
354+
stats[3+offset],stats[4+offset],stats[5+offset],
355+
stats[6+offset],stats[7+offset],stats[8+offset],
356+
stats[9+offset],
357+
nsFromTimeSpecBigInt(stats[10+offset],stats[11+offset]),
358+
nsFromTimeSpecBigInt(stats[12+offset],stats[13+offset]),
359+
nsFromTimeSpecBigInt(stats[14+offset],stats[15+offset]),
360+
nsFromTimeSpecBigInt(stats[16+offset],stats[17+offset])
361+
);
362+
}
363+
returnnewStats(
364+
stats[0+offset],stats[1+offset],stats[2+offset],
365+
stats[3+offset],stats[4+offset],stats[5+offset],
366+
stats[6+offset],stats[7+offset],stats[8+offset],
367+
stats[9+offset],
368+
msFromTimeSpec(stats[10+offset],stats[11+offset]),
369+
msFromTimeSpec(stats[12+offset],stats[13+offset]),
370+
msFromTimeSpec(stats[14+offset],stats[15+offset]),
371+
msFromTimeSpec(stats[16+offset],stats[17+offset])
372+
);
321373
}
322374

323375
functionstringToFlags(flags){
@@ -453,6 +505,7 @@ function warnOnNonPortableTemplate(template) {
453505

454506
module.exports={
455507
assertEncoding,
508+
BigIntStats,// for testing
456509
copyObject,
457510
Dirent,
458511
getDirents,

‎src/env.h‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,32 @@ struct PackageConfig {
102102
};
103103
} // namespace loader
104104

105+
enumclassFsStatsOffset {
106+
kDev = 0,
107+
kMode,
108+
kNlink,
109+
kUid,
110+
kGid,
111+
kRdev,
112+
kBlkSize,
113+
kIno,
114+
kSize,
115+
kBlocks,
116+
kATimeSec,
117+
kATimeNsec,
118+
kMTimeSec,
119+
kMTimeNsec,
120+
kCTimeSec,
121+
kCTimeNsec,
122+
kBirthTimeSec,
123+
kBirthTimeNsec,
124+
kFsStatsFieldsNumber
125+
};
126+
105127
// Stat fields buffers contain twice the number of entries in an uv_stat_t
106128
// because `fs.StatWatcher` needs room to store 2 `fs.Stats` instances.
107-
constexprsize_tkFsStatsFieldsNumber = 14;
108-
constexprsize_tkFsStatsBufferLength = kFsStatsFieldsNumber * 2;
129+
constexprsize_tkFsStatsBufferLength =
130+
static_cast<size_t>(FsStatsOffset::kFsStatsFieldsNumber) * 2;
109131

110132
// PER_ISOLATE_* macros: We have a lot of per-isolate properties
111133
// and adding and maintaining their getters and setters by hand would be

‎src/node_file.cc‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2197,10 +2197,13 @@ void Initialize(Local<Object> target,
21972197

21982198
env->SetMethod(target, "mkdtemp", Mkdtemp);
21992199

2200-
target->Set(context,
2201-
FIXED_ONE_BYTE_STRING(isolate, "kFsStatsFieldsNumber"),
2202-
Integer::New(isolate, kFsStatsFieldsNumber))
2203-
.Check();
2200+
target
2201+
->Set(context,
2202+
FIXED_ONE_BYTE_STRING(isolate, "kFsStatsFieldsNumber"),
2203+
Integer::New(
2204+
isolate,
2205+
static_cast<int32_t>(FsStatsOffset::kFsStatsFieldsNumber)))
2206+
.Check();
22042207

22052208
target->Set(context,
22062209
FIXED_ONE_BYTE_STRING(isolate, "statValues"),

0 commit comments

Comments
 (0)