Commit Graph

16927 Commits

Author SHA1 Message Date
Gregory Szorc
0ee2ea3be0 changelog: disable delta chains
This patch disables delta chains on changelogs. After this patch, new
entries on changelogs - including existing changelogs - will be stored
as the fulltext of that data (likely compressed). No delta computation
will be performed.

An overview of delta chains and data justifying this change follows.

Revlogs try to store entries as a delta against a previous entry (either
a parent revision in the case of generaldelta or the previous physical
revision when not using generaldelta). Most of the time this is the
correct thing to do: it frequently results in less CPU usage and smaller
storage.

Delta chains are most effective when the base revision being deltad
against is similar to the current data. This tends to occur naturally
for manifests and file data, since only small parts of each tend to
change with each revision. Changelogs, however, are a different story.

Changelog entries represent changesets/commits. And unless commits in a
repository are homogonous (same author, changing same files, similar
commit messages, etc), a delta from one entry to the next tends to be
relatively large compared to the size of the entry. This means that
delta chains tend to be short. How short? Here is the full vs delta
revision breakdown on some real world repos:

Repo             % Full    % Delta   Max Length
hg                45.8       54.2        6
mozilla-central   42.4       57.6        8
mozilla-unified   42.5       57.5       17
pypy              46.1       53.9        6
python-zstandard  46.1       53.9        3

(I threw in python-zstandard as an example of a repo that is homogonous.
It contains a small Python project with changes all from the same
author.)

Contrast this with the manifest revlog for these repos, where 99+% of
revisions are deltas and delta chains run into the thousands.

So delta chains aren't as useful on changelogs. But even a short delta
chain may provide benefits. Let's measure that.

Delta chains may require less CPU to read revisions if the CPU time
spent reading smaller deltas is less than the CPU time used to
decompress larger individual entries. We can measure this via
`hg perfrevlog -c -d 1` to iterate a revlog to resolve each revision's
fulltext. Here are the results of that command on a repo using delta
chains in its changelog and on a repo without delta chains:

hg (forward)
! wall 0.407008 comb 0.410000 user 0.410000 sys 0.000000 (best of 25)
! wall 0.390061 comb 0.390000 user 0.390000 sys 0.000000 (best of 26)

hg (reverse)
! wall 0.515221 comb 0.520000 user 0.520000 sys 0.000000 (best of 19)
! wall 0.400018 comb 0.400000 user 0.390000 sys 0.010000 (best of 25)

mozilla-central (forward)
! wall 4.508296 comb 4.490000 user 4.490000 sys 0.000000 (best of 3)
! wall 4.370222 comb 4.370000 user 4.350000 sys 0.020000 (best of 3)

mozilla-central (reverse)
! wall 5.758995 comb 5.760000 user 5.720000 sys 0.040000 (best of 3)
! wall 4.346503 comb 4.340000 user 4.320000 sys 0.020000 (best of 3)

mozilla-unified (forward)
! wall 4.957088 comb 4.950000 user 4.940000 sys 0.010000 (best of 3)
! wall 4.660528 comb 4.650000 user 4.630000 sys 0.020000 (best of 3)

mozilla-unified (reverse)
! wall 6.119827 comb 6.110000 user 6.090000 sys 0.020000 (best of 3)
! wall 4.675136 comb 4.670000 user 4.670000 sys 0.000000 (best of 3)

pypy (forward)
! wall 1.231122 comb 1.240000 user 1.230000 sys 0.010000 (best of 8)
! wall 1.164896 comb 1.160000 user 1.160000 sys 0.000000 (best of 9)

pypy (reverse)
! wall 1.467049 comb 1.460000 user 1.460000 sys 0.000000 (best of 7)
! wall 1.160200 comb 1.170000 user 1.160000 sys 0.010000 (best of 9)

The data clearly shows that it takes less wall and CPU time to resolve
revisions when there are no delta chains in the changelogs, regardless
of the direction of traversal. Furthermore, not using a delta chain
means that fulltext resolution in reverse is as fast as iterating
forward. So not using delta chains on the changelog is a clear CPU win
for reading operations.

An example of a user-visible operation showing this speed-up is revset
evaluation. Here are results for
`hg perfrevset 'author(gps) or author(mpm)'`:

hg
! wall 1.655506 comb 1.660000 user 1.650000 sys 0.010000 (best of 6)
! wall 1.612723 comb 1.610000 user 1.600000 sys 0.010000 (best of 7)

mozilla-central
! wall 17.629826 comb 17.640000 user 17.600000 sys 0.040000 (best of 3)
! wall 17.311033 comb 17.300000 user 17.260000 sys 0.040000 (best of 3)

What about 00changelog.i size?

Repo                Delta Chains     No Delta Chains
hg                    7,033,250         6,976,771
mozilla-central      82,978,748        81,574,623
mozilla-unified      88,112,349        86,702,162
pypy                 20,740,699        20,659,741

The data shows that removing delta chains from the changelog makes the
changelog smaller.

Delta chains are also used during changegroup generation. This
operation essentially converts a series of revisions to one large
delta chain. And changegroup generation is smart: if the delta in
the revlog matches what the changegroup is emitting, it will reuse
the delta instead of recalculating it. We can measure the impact
removing changelog delta chains has on changegroup generation via
`hg perfchangegroupchangelog`:

hg
! wall 1.589245 comb 1.590000 user 1.590000 sys 0.000000 (best of 7)
! wall 1.788060 comb 1.790000 user 1.790000 sys 0.000000 (best of 6)

mozilla-central
! wall 17.382585 comb 17.380000 user 17.340000 sys 0.040000 (best of 3)
! wall 20.161357 comb 20.160000 user 20.120000 sys 0.040000 (best of 3)

mozilla-unified
! wall 18.722839 comb 18.720000 user 18.680000 sys 0.040000 (best of 3)
! wall 21.168075 comb 21.170000 user 21.130000 sys 0.040000 (best of 3)

pypy
! wall 4.828317 comb 4.830000 user 4.820000 sys 0.010000 (best of 3)
! wall 5.415455 comb 5.420000 user 5.410000 sys 0.010000 (best of 3)

The data shows eliminating delta chains makes the changelog part of
changegroup generation slower. This is expected since we now have to
compute deltas for revisions where we could recycle the delta before.

It is worth putting this regression into context of overall changegroup
times. Here is the rough total CPU time spent in changegroup generation
for various repos while using delta chains on the changelog:

Repo              CPU Time (s)    CPU Time w/ compression
hg                  4.50              7.05
mozilla-central   111.1             222.0
pypy               28.68             75.5

Before compression, removing delta chains from the changegroup adds
~4.4% overhead to hg changegroup generation, 1.3% to mozilla-central,
and 2.0% to pypy. When you factor in zlib compression, these percentages
are roughly divided by 2.

While the increased CPU usage for changegroup generation is unfortunate,
I think it is acceptable because the percentage is small, server
operators (those likely impacted most by this) have other mechanisms
to mitigate CPU consumption (namely reducing zlib compression level and
pre-generated clone bundles), and because there is room to optimize this
in the future. For example, we could use the nullid as the base revision,
effectively encoding the full revision for each entry in the changegroup.
When doing this, `hg perfchangegroupchangelog` nearly halves:

mozilla-unified
! wall 21.168075 comb 21.170000 user 21.130000 sys 0.040000 (best of 3)
! wall 11.196461 comb 11.200000 user 11.190000 sys 0.010000 (best of 3)

This looks very promising as a future optimization opportunity.

It's worth that the changes in test-acl.t to the changegroup part size.
This is because revision 6 in the changegroup had a delta chain of
length 2 before and after this patch the base revision is nullrev.
When the base revision is nullrev, cg2packer.deltaparent() hardcodes
the *previous* revision from the changegroup as the delta parent.
This caused the delta in the changegroup to switch base revisions,
the delta to change, and the size to change accordingly. While the
size increased in this case, I think sizes will remain the same
on average, as the delta base for changelog revisions doesn't matter
too much (as this patch shows). So, I don't consider this a regression.
2016-10-13 12:50:27 +02:00
Gregory Szorc
748ec42334 revlog: add instance variable controlling delta chain use
This is to support disabling delta chains on the changelog in a
subsequent patch.
2016-09-24 12:25:37 -07:00
Gregory Szorc
eb9f859c39 changegroup: document deltaparent's choice of previous revision
As part of debugging low-level changegroup generation, I came across
what I initially thought was a weird behavior: changegroup v2 is
choosing the previous revision in the changegroup as a delta base
instead of p1. I was tempted to rewrite this to use p1, as p1
will delta better than prev in the common case. However, I realized
that taking p1 as the base would potentially require resolving a
revision fulltext and thus require more CPU for e.g. server-side
processing of getbundle requests.

This patch tweaks the code comment to note the choice of behavior.
It also notes there is room for a flag or config option to tweak
this behavior later: using p1 as the delta base would likely make
changegroups smaller at the expense of more CPU, which could be
beneficial for things like clone bundles.
2016-10-13 12:49:47 +02:00
Pierre-Yves David
d482e52866 help: backout 6f89f03ad369 (mark boolean flags with [no-] in help) for now
The ability to negate any boolean flags itself is great, but I think we are not
ready to expose the help side of it yet.

First, while there exist a handful of such flags whose default value can be
changed (eg: git diff, patchwork confirmation), there is only a few of them. The
users who benefit the most from this change are alias users and large
installation that can deploy extension to change behavior (eg: facebook
tweakdefault).  So the majority of user who will be affected by a large change
to command help that is not yet relevant to them. (I expect this to become
relevant when ui.progressive start to exists).

Below is an example of the impact of the new help on 'hg help diff':

  -r --rev REV [+]              revision
  -c --change REV               change made by revision
  -a --[no-]text                treat all files as text
  -g --[no-]git                 use git extended diff format
     --[no-]nodates             omit dates from diff headers
     --[no-]noprefix            omit a/ and b/ prefixes from filenames
  -p --[no-]show-function       show which function each change is in
     --[no-]reverse             produce a diff that undoes the changes
  -w --[no-]ignore-all-space    ignore white space when comparing lines
  -b --[no-]ignore-space-change ignore changes in the amount of white space
  -B --[no-]ignore-blank-lines  ignore changes whose lines are all blank
  -U --unified NUM              number of lines of context to show
     --[no-]stat                output diffstat-style summary of changes
     --root DIR                 produce diffs relative to subdirectory
  -I --include PATTERN [+]      include names matching the given patterns
  -X --exclude PATTERN [+]      exclude names matching the given patterns
  -S --[no-]subrepos            recurse into subrepositories

Another issue with the current state of help, the default value for the
flag is not conveyed to the user. For example in the 'backout' help, there is
no real distinction between "--[no-]backup" (default to True) and "--[no-]keep"
(default) to False:

  --[no-]backup        no backups
  --[no-]keep          do not modify working directory during strip

In addition, I've discussed with Augie Fackler and the last batch of the work on
this have burned him out quite some. Therefore he is not intending to perform
any more work on this topic. Quoting him, he would rather see the help part
backed out than spending more time on it.

I do not think we are ready to expose this to users in 4.0 (freeze in a week),
especially because we cannot expect quick improvement on these aspect as this
topic no longer have an owner. We should be able to reintroduce that change in
the future when someone get back on it and the main issues are solves:

* Introduction of  ui.progressive makes it relevant for a majority of user,
* Current default value are efficiently conveyed to the user.

(In addition, the excerpt from diff help show that we still have some issue with
some negative option like '--nodates' so further improvement are probably
welcome there.)
2016-10-09 03:11:18 +02:00
Augie Fackler
530be6f190 copy: distinguish "file exists" cases and add a hint (BC)
Users that want to add a copy record to an existing commit with 'hg
commit --amend' should be guided towards this workflow, rather than
reaching for some sort of uncommit-recommit flow. As part of this,
distinguish in the top-line error message whether the file merely
already exists (untracked) on disk or the file already exists in
history.

The full list of copy and rename cases and how they interact with
flags are listed below:

target exists  --after  --force  |  action
      n            n      *    |  copy
      n            y      *    |  (1)
  untracked        n      n    |  (4) NEWHINT
  untracked        n      y    |  (3)
  untracked        y      *    |  (2)
      y            n      n    |  (4) NEWHINT
      y            n      y    |  (3)
      y            y      n    |  (2)
      y            y      y    |  (3)
   deleted         n      n    |  copy
   deleted         n      y    |  (3)
   deleted         y      n    |  (1)
   deleted         y      y    |  (1)

* = don't care
(1) <src>: not recording move - <target> does not exist
(2) preserve target contents
(3) replace target contents
(4) <target>: not overwriting - file {exists,already committed}

Credit to Kevin for wholly rewriting my table to cover more cases we
discovered at the sprint.

I think this change gets the hints correct in all cases, but I'd
appreciate close inspection of the test cases to make sure I haven't
gotten turned around in here.
2016-09-19 17:15:39 -04:00
Mads Kiilerich
7afa73604d largefiles: use context for file closing
Make the code slightly smaller and safer (and more deeply indented).
2016-10-08 00:59:41 +02:00
Gregory Szorc
48ea4c11ea dirs: add comment about _PyBytes_Resize
So readers have a canonical function to compare this code to.
2016-10-13 10:59:29 +02:00
Pierre-Yves David
163070ae3d checkcopies: extract the '_related' closure
There is not need for it to be a closure.
2016-10-11 01:29:08 +02:00
Pierre-Yves David
2d670597fe checkcopies: add an inline comment about the '_related' call
This helps understanding the flow of the function.
2016-10-08 23:00:55 +02:00
Pierre-Yves David
71b0c4ef9c checkcopies: minor change to comment
This helped me understand the refactoring so this must be helpful.
2016-10-08 19:03:16 +02:00
Pierre-Yves David
1f966aa892 checkcopies: rename 'ca' to 'base'
This variable was named after the common ancestor. It is actually the merge
base that might differ from the common ancestor in the graft case. We rename the
variable before a larger refactoring to clarify the situation.
2016-10-08 18:38:42 +02:00
Pierre-Yves David
023c7518b5 bisect: extra a small initialisation outside of a loop
Having initialisation done during the first iteration is cute, but can be
avoided.
2016-08-24 05:09:46 +02:00
Martijn Pieters
6c2c90ea4c pycompat: only accept a bytestring filepath in Python 2 2016-10-10 23:11:15 +01:00
Augie Fackler
88491409f0 debuginstall: use %d instead of %s for formatting an int
% formatting on bytes on Python 3 is pickier about which % character
we specify.
2016-10-09 09:42:46 -04:00
Pierre-Yves David
a3ad6827e4 bisect: build a displayer only once
There is multiple spot using this, building it early will help to extract more
of the logic into the bisect module.
2016-08-24 05:06:21 +02:00
Pierre-Yves David
440aae6fd1 bisect: factor commonly update sequence
For now, This remains a closure in the module to avoid circular import with used
module.
2016-08-24 05:04:46 +02:00
Pierre-Yves David
7afebe8fb3 bisect: move check_state into the bisect module
Now that the function is simpler, we resume our quest to move the logic into the
bisect module. In the process, we add basic documentation.
2016-08-24 04:25:20 +02:00
Pierre-Yves David
1864a91d18 bisect: simplify conditional in 'check_state'
Now that extra code about "updating" flag have been removed, we can simplify the
condition flow and remove a level.
2016-08-24 04:23:13 +02:00
Pierre-Yves David
d737fb6fef bisect: remove code about "update-flag" in check_state
Now that the flag dedicated to updating the flag are handled earlier, we do not
need to handle them in the 'check_state' function.
2016-08-24 04:22:40 +02:00
Pierre-Yves David
c0f3c6bafe bisect: rename 'check_code' to match our naming scheme
We need to do it early, otherwise 'check-commit' will complain every time we
touch it.
2016-10-09 03:50:55 +02:00
Pierre-Yves David
e6d1c84c27 bisect: minor movement of code handle flag updating state
The code flag handling is quite complicated, we are moving code around to
prepare further simplification.
2016-08-24 04:48:17 +02:00
Martijn Pieters
74d3bea9ae py3: add an os.fsencode backport to ease path handling 2016-10-09 17:44:23 +02:00
Martijn Pieters
e20d571ccf py3: a second argument to open can't be bytes
This fixes open(filename, 'r'), open(filename, 'w'), etc. calls. In Python
3, that second argument *must* be a string, you can't use bytes.

The fix is the same as used with getattr() (where the second argument must
also always be a string); in the tokenizer, where we detect calls, if there
is something that looks like a call to open (and is not an attribute, so
the previous token is not a "." dot) then make sure that that second
argument is not converted to a `bytes` object instead.

There is some remaining issue where the current transformer will also rewrite

  open(f('foo')).

However this also affect function for which we perform similar rewrite
('getattr', 'setattr', 'hasattr', 'safehasattr') and will be dealt with in a
follow up.
2016-10-09 14:10:01 +02:00
Simon Farnsworth
d1e0848d7f templater: handle division by zero in arithmetic
For now, just turn it to an abort.
2016-10-09 08:09:20 -07:00
Simon Farnsworth
d5bf3ea399 templater: provide arithmetic operations on integers
The termwidth template keyword is of limited use without some way to ensure
that margins are respected.

Provide a full set of arithmetic operators (four basic operations plus the
mod function, defined to match Python's // for division), so that you can
create termwidth based layouts that match the user's terminal size
2016-10-09 05:51:04 -07:00
Gregory Szorc
d61a5b6632 parsers: move PyInt aliasing out of util.h
The PyInt aliasing is only used by parsers.c. Since we don't want to
encourage the use of PyInt parsing, move the aliasing to parsers.c.
2016-10-09 13:50:53 +02:00
Gregory Szorc
ac2a04b10d osutil: use PyLongObject on Python 3 for listdir_slot
This code looks performance sensitive. So let's retain PyIntObject on
Python 2 and use PyLongObject explicitly on Python 3.
2016-10-09 13:47:46 +02:00
Gregory Szorc
809ce99930 osutil: use PyLongObject in recvfds
PyIntObject doesn't exist in Python 3. While PyIntObject is preferred
on Python 2 because it is a fixed capacity and faster, the difference
between PyIntObject and PyLongObject for scenarios where performance
isn't critical or the caller isn't performing type checking shouldn't
be relevant.

So change recvfds to return a list of longs instead of ints on Python
2.
2016-10-09 13:41:18 +02:00
Pulkit Goyal
f139198734 py3: use encoding.environ instead of os.environ
This complains while running hg version on Python 3.5
2016-10-09 12:37:10 +02:00
Martijn Pieters
25a79def1c store: py26 compat, don't use a dict comprehension 2016-10-09 12:58:22 +02:00
Gregory Szorc
d30141b4f3 dirs: document performance reasons for bypassing Python C API
So someone isn't tempted to change it.
2016-10-08 16:51:18 +02:00
Gregory Szorc
c066170094 dirs: port PyInt code to work on Python 3
PyIntObject no longer exists in Python 3. Instead, there is
PyLongObject.

Furthermore, PyInt_AS_LONG is a macro referencing a struct member.
PyInt_AS_LONG doesn't exist in Python 3 and PyLong_AS_LONG is a
#define for PyLong_AsLong, which is a function. So assigning to the
return value of PyLong_AS_LONG doesn't work.

This patch introduces a macro for obtaining the value of an
integer-like type that works on Python 2 and Python 3. On
Python 3, we access the struct field of the underlying
PyLongObjet directly, without overflow checking. This is
essentially the same as what Python 2 was doing except using a
PyLong instead of a PyInt.
2016-10-08 16:20:21 +02:00
Gregory Szorc
17698cdbdb dirs: convert PyString to PyBytes
PyStringObject was renamed to PyBytes in Python 3 along with the
corresponding PyString* functions and macros. PyString* doesn't
exist in Python 3. But PyBytes* is an alias to PyString in Python 2.
So rewrite PyString* to PyBytes* for Python 2/3 dual compatibility.
2016-10-08 14:31:59 +02:00
Gregory Szorc
e7e3331fdd dirs: inline string macros
The old code happened to work because of how the macro was defined.
This no longer works in Python 3. Furthermore, assigning to a macro
just feels weird. So just inline the macro.
2016-10-08 16:02:51 +02:00
Gregory Szorc
216178bf54 parsers: use PyVarObject_HEAD_INIT
The macro changed slightly in Python 3, introducing curly brackets
that somehow confuse Clang into issuing a ton of compiler warnings.
Using PyVarObject_HEAD_INIT makes these go away.

It's worth noting that the code is identical: the 2nd argument to
PyVarObject_HEAD_INIT is assigned to the ob_size field and is
inserted immediately after "PyObject_HEAD_INIT(type)" is generated.
Compilers are weird.
2016-10-08 22:44:02 +02:00
Gregory Szorc
c76037ee62 pathencode: use Py_SIZE directly
On Python 2, PyBytes_GET_SIZE is the same as PyString_GET_SIZE which
is the same as Py_SIZE which resolves to a struct member.

On Python 3, PyBytes_GET_SIZE is
"(assert(PyBytes_Check(op)),Py_SIZE(op))". The compiler barfs when
assigning to this version.

This patch simply changes PyBytes_GET_SIZE to Py_SIZE. On Python 2,
there is no effective change in behavior. On Python 3, we drop the
PyBytes_Check(). However, in all cases we have explicitly created
a PyBytesObject in the same function, so the PyBytes_Check() is
guaranteed to be true. Despite this, code changes over time, so
I've added added assert() in all callers so we can catch this in
debug builds.

With this patch, all mercurial.* C extensions now compile on Python 3
on my OS X machine. There are several compiler warnings and I'm sure
there are incompatibilities with Python 3, including possibly
segfaults. But it is a milestone.
2016-10-08 22:21:22 +02:00
Gregory Szorc
83437de093 util: remove PyString* aliases on Python 3
We no longer have any users of the legacy PyString* functions. We no
longer need these redefinitions.

After this change, the only reference to "PyString" in the repo is in
watchman's C extension. That isn't our code and porting Mercurial
extensions to Python 3 is not a high priority at the moment. watchman's
C extension will be dealt with later.
2016-10-08 22:04:56 +02:00
Gregory Szorc
bd245cb051 parsers: convert PyString* to PyBytes*
With this change, we no longer have any occurrences of "PyString" in
our C extensions.
2016-10-08 22:02:29 +02:00
Gregory Szorc
d59467145b pathencode: convert PyString* to PyBytes* 2016-10-08 22:01:07 +02:00
Gregory Szorc
fb1f96e995 osutil: convert PyString* to PyBytes*
Continuing the conversion from PyString* to PyBytes*.
2016-10-08 21:58:55 +02:00
Gregory Szorc
0c43bebd47 manifest: convert PyString* to PyBytes*
Python 2.6 introduced PyBytesObject and PyBytes* as aliases for
PyStringObject and PyString*. So on Python 2.6+, PyBytes* and PyString*
are identical and this patch should be a no-op.

On Python 3, PyStringObject is effectively renamed to PyUnicodeObject
and PyBytesObject becomes the main type for byte strings.

This patch begins the process of mass converting PyString* to PyBytes*
so the C extensions use the correct type on Python 3.
2016-10-08 21:57:55 +02:00
Yuya Nishihara
512724e1f5 merge: update doc of manifestmerge() per ce7e15f82b44
p1 was renamed to wctx by ce7e15f82b44.
2016-10-02 17:31:32 +09:00
Gregory Szorc
101896816d util: document we want Python type mapping to be temporary
I think remapping Python C API types and functions is not a great
approach. I'd prefer this whole #ifdef disappeared. Add a comment
so we don't forget about it.
2016-10-08 19:16:50 +02:00
Gregory Szorc
e28802538d util: define PyInt_Type on Python 3
util.h attempts to wallpaper over C API differences between Python 2
and 3. This is not the correct approach where performance is critical.
But it is good enough for the current state of the Python 3 port.
2016-10-08 19:02:44 +02:00
Gregory Szorc
8d34ccc102 parsers: return NULL from PyInit_parsers on Python 3
This function must return a PyObject* or the compiler complains.
2016-10-08 17:51:29 +02:00
Gábor Stefanik
9fbfde0b09 mail: take --encoding and HGENCODING into account
Fall back to our encoding strategy for sending MIME text
that's neither ASCII nor UTF-8.
2016-10-05 13:45:22 +02:00
Simon Farnsworth
c7fbc87ac7 template: provide a termwidth keyword (issue5395)
We want to provide terminal-sized output. As a starting point, expose the
terminal width to the templater for use in things like fill.
2016-10-08 02:26:48 -07:00
Augie Fackler
10de2c7e4f util: ensure forwarded attrs are set in globals() as sysstr
Custom module importer strikes again.
2016-10-08 08:36:39 -04:00
Augie Fackler
867b91b167 pycompat: when setting attrs, ensure we use sysstr
The custom module importer was making these bytes, so when we poked
values into self.__dict__ we had bytes instead of unicode on py3 and
it didn't work.
2016-10-08 08:35:43 -04:00
Augie Fackler
968375bf81 i18n: make the locale directory name the same string type as the datapath 2016-10-08 05:26:18 -04:00