couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From vatam...@apache.org
Subject [16/50] couch-replicator commit: updated refs/heads/63012-scheduler to 27a5eae
Date Tue, 14 Mar 2017 19:26:11 GMT
Add start_time and last_updated fields to _scheduler/ endpoints output

Also for consistency add _replication_start_time field to completed and failed
replication document updates.

`start_time` is defined as the time since the replication first was noticed by
the replicator. For a document-based replication it is when the document update
event reached couch replicator and the replication record was parsed from it.
For a _replicate replication it is the time when the POST request body was
parsed.

`last_updated` is time of last state update.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/commit/ab198aae
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/tree/ab198aae
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/diff/ab198aae

Branch: refs/heads/63012-scheduler
Commit: ab198aaeca6da5a3287a507f2e810f8d7c483a91
Parents: 4e9b956
Author: Nick Vatamaniuc <vatamane@apache.org>
Authored: Wed Nov 2 13:57:37 2016 -0400
Committer: Nick Vatamaniuc <vatamane@apache.org>
Committed: Thu Nov 3 16:37:42 2016 -0400

----------------------------------------------------------------------
 src/couch_replicator.erl                      | 33 +++++++++++++----
 src/couch_replicator.hrl                      |  3 +-
 src/couch_replicator_doc_processor.erl        | 43 +++++++++++++++-------
 src/couch_replicator_doc_processor_worker.erl |  5 ++-
 src/couch_replicator_docs.erl                 | 41 ++++++---------------
 src/couch_replicator_js_functions.hrl         | 10 ++++-
 src/couch_replicator_scheduler.erl            | 32 ++++++++--------
 src/couch_replicator_scheduler_job.erl        | 13 ++++---
 src/couch_replicator_utils.erl                |  8 ++++
 9 files changed, 113 insertions(+), 75 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/blob/ab198aae/src/couch_replicator.erl
----------------------------------------------------------------------
diff --git a/src/couch_replicator.erl b/src/couch_replicator.erl
index a8d4608..0a159c3 100644
--- a/src/couch_replicator.erl
+++ b/src/couch_replicator.erl
@@ -47,8 +47,9 @@
     {ok, {cancelled, binary()}} |
     {error, any()}.
 replicate(PostBody, Ctx) ->
-    {ok, #rep{id = RepId, options = Options, user_ctx = UserCtx} = Rep} =
-        couch_replicator_utils:parse_rep_doc(PostBody, Ctx),
+    {ok, Rep0} = couch_replicator_utils:parse_rep_doc(PostBody, Ctx),
+    Rep = Rep0#rep{start_time = os:timestamp()},
+    #rep{id = RepId, options = Options, user_ctx = UserCtx} = Rep,
     case get_value(cancel, Options, false) of
     true ->
         case get_value(id, Options, nil) of
@@ -207,7 +208,15 @@ handle_replicator_doc_query({row, Props}, {Db, Cb, UserAcc, States})
->
     DocId = couch_util:get_value(id, Props),
     DocStateBin = couch_util:get_value(key, Props),
     DocState = erlang:binary_to_existing_atom(DocStateBin, utf8),
-    StateInfo = couch_util:get_value(value, Props),
+    MapValue = couch_util:get_value(value, Props),
+    {StartTime, StateTime, StateInfo} = case MapValue of
+        [StartT, StateT, Info] ->
+            {StartT, StateT, Info};
+        _Other ->
+            % Handle the case where the view code was upgraded but new view code
+            % wasn't updated yet (before a _scheduler/docs request was made)
+            {null, null, null}
+    end,
     % Set the error_count to 1 if failed. This is mainly for consistency as
     % jobs from doc_processor and scheduler will have that value set
     ErrorCount = case DocState of failed -> 1; _ -> 0 end,
@@ -219,7 +228,9 @@ handle_replicator_doc_query({row, Props}, {Db, Cb, UserAcc, States}) ->
                 {id, null},
                 {state, DocState},
                 {error_count, ErrorCount},
-                {info, StateInfo}
+                {info, StateInfo},
+                {last_updated, StateTime},
+                {start_time, StartTime}
             ]},
             {ok, {Db, Cb, Cb(EjsonInfo, UserAcc), States}};
         false ->
@@ -268,12 +279,16 @@ doc_from_db(RepDb, DocId, UserCtx) ->
     case fabric:open_doc(RepDb, DocId, [UserCtx, ejson_body]) of
         {ok, Doc} ->
             {Props} = couch_doc:to_json_obj(Doc, []),
-            State = couch_util:get_value(<<"_replication_state">>, Props, null),
+            State = get_value(<<"_replication_state">>, Props, null),
+            StartTime = get_value(<<"_replication_start_time">>, Props, null),
+            StateTime = get_value(<<"_replication_state_time">>, Props, null),
             {StateInfo, ErrorCount} = case State of
                 <<"completed">> ->
-                    {couch_util:get_value(<<"_replication_stats">>, Props, null),
0};
+                    Info = get_value(<<"_replication_stats">>, Props, null),
+                    {Info, 0};
                 <<"failed">> ->
-                    {couch_util:get_value(<<"_replication_state_reason">>, Props,
null), 1};
+                    Info = get_value(<<"_replication_state_reason">>, Props,
null),
+                    {Info, 1};
                 _OtherState ->
                     {null, 0}
             end,
@@ -283,7 +298,9 @@ doc_from_db(RepDb, DocId, UserCtx) ->
                 {id, null},
                 {state, State},
                 {error_count, ErrorCount},
-                {info, StateInfo}
+                {info, StateInfo},
+                {start_time, StartTime},
+                {last_updated, StateTime}
             ]}};
          {not_found, _Reason} ->
             {error, not_found}

http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/blob/ab198aae/src/couch_replicator.hrl
----------------------------------------------------------------------
diff --git a/src/couch_replicator.hrl b/src/couch_replicator.hrl
index f116ee2..339e162 100644
--- a/src/couch_replicator.hrl
+++ b/src/couch_replicator.hrl
@@ -21,7 +21,8 @@
     type = db :: atom() | '_',
     view = nil :: any() | '_',
     doc_id :: any() | '_',
-    db_name = null :: null | binary() | '_'
+    db_name = null :: null | binary() | '_',
+    start_time :: erlang:timestamp() | '_'
 }).
 
 -type rep_id() :: {string(), string()}.

http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/blob/ab198aae/src/couch_replicator_doc_processor.erl
----------------------------------------------------------------------
diff --git a/src/couch_replicator_doc_processor.erl b/src/couch_replicator_doc_processor.erl
index eadea5d..06ec743 100644
--- a/src/couch_replicator_doc_processor.erl
+++ b/src/couch_replicator_doc_processor.erl
@@ -46,7 +46,8 @@
     filter :: filter_type() | '_',
     info :: binary() | nil | '_',
     errcnt :: non_neg_integer() | '_',
-    worker :: reference() | nil | '_'
+    worker :: reference() | nil | '_',
+    last_updated :: erlang:timestamp() | '_'
 }).
 
 
@@ -79,7 +80,8 @@ db_change(DbName, {ChangeProps} = Change, Server) ->
     _Tag:Error ->
         {RepProps} = get_json_value(doc, ChangeProps),
         DocId = get_json_value(<<"_id">>, RepProps),
-        couch_replicator_docs:update_failed(DbName, DocId, Error)
+        Timestamp = os:timestamp(),
+        couch_replicator_docs:update_failed(DbName, DocId, Error, Timestamp)
     end,
     Server.
 
@@ -127,7 +129,7 @@ process_updated({DbName, _DocId} = Id, JsonRepDoc) ->
     % failure in the document. User will have to delete and re-create the document
     % to fix the problem.
     Rep0 = couch_replicator_docs:parse_rep_doc_without_id(JsonRepDoc),
-    Rep = Rep0#rep{db_name = DbName},
+    Rep = Rep0#rep{db_name = DbName, start_time = os:timestamp()},
     Filter = case couch_replicator_filters:parse(Rep#rep.options) of
     {ok, nil} ->
         nil;
@@ -213,7 +215,8 @@ updated_doc(Id, Rep, Filter) ->
         filter = Filter,
         info = nil,
         errcnt = 0,
-        worker = nil
+        worker = nil,
+        last_updated = os:timestamp()
     },
     true = ets:insert(?MODULE, Row),
     ok = maybe_start_worker(Id),
@@ -224,7 +227,8 @@ updated_doc(Id, Rep, Filter) ->
 worker_returned(Ref, Id, {ok, RepId}) ->
     case ets:lookup(?MODULE, Id) of
     [#rdoc{worker = Ref} = Row] ->
-        true = ets:insert(?MODULE, update_docs_row(Row, RepId)),
+        NewRow = update_docs_row(Row, RepId),
+        true = ets:insert(?MODULE, NewRow#rdoc{last_updated = os:timestamp()}),
         ok = maybe_start_worker(Id);
     _ ->
         ok  % doc could have been deleted, ignore
@@ -239,7 +243,8 @@ worker_returned(Ref, Id, {temporary_error, Reason}) ->
             state = error,
             info = Reason,
             errcnt = ErrCnt + 1,
-            worker = nil
+            worker = nil,
+            last_updated = os:timestamp()
         },
         true = ets:insert(?MODULE, NewRow),
         ok = maybe_start_worker(Id);
@@ -433,7 +438,9 @@ ejson_doc(#rdoc{state = RepState} = RDoc, _HealthThreshold) ->
        id = {DbName, DocId},
        info = StateInfo,
        rid = RepId,
-       errcnt = ErrorCount
+       errcnt = ErrorCount,
+       last_updated = StateTime,
+       rep = Rep
     } = RDoc,
     {[
         {doc_id, DocId},
@@ -442,7 +449,9 @@ ejson_doc(#rdoc{state = RepState} = RDoc, _HealthThreshold) ->
         {state, RepState},
         {info, ejson_state_info(StateInfo)},
         {error_count, ErrorCount},
-        {node, node()}
+        {node, node()},
+        {last_updated, couch_replicator_utils:iso8601(StateTime)},
+        {start_time, couch_replicator_utils:iso8601(Rep#rep.start_time)}
     ]}.
 
 
@@ -510,7 +519,7 @@ t_regular_change() ->
 % a running job with same Id found.
 t_change_with_existing_job() ->
     ?_test(begin
-        mock_existing_jobs_lookup([#rep{id = ?R2}]),
+        mock_existing_jobs_lookup([test_rep(?R2)]),
         ?assertEqual(ok, process_change(?DB, change())),
         ?assert(ets:member(?MODULE, {?DB, ?DOC1})),
         ?assert(started_worker({?DB, ?DOC1}))
@@ -520,7 +529,7 @@ t_change_with_existing_job() ->
 % Change is a deletion, and job is running, so remove job.
 t_deleted_change() ->
     ?_test(begin
-        mock_existing_jobs_lookup([#rep{id = ?R2}]),
+        mock_existing_jobs_lookup([test_rep(?R2)]),
         ?assertEqual(ok, process_change(?DB, deleted_change())),
         ?assert(removed_job(?R2))
     end).
@@ -615,6 +624,10 @@ t_ejson_docs() ->
         EJsonDocs = docs([]),
         ?assertMatch([{[_|_]}], EJsonDocs),
         [{DocProps}] = EJsonDocs,
+        {value, StateTime, DocProps1} = lists:keytake(last_updated, 1, DocProps),
+        ?assertMatch({last_updated, BinVal1} when is_binary(BinVal1), StateTime),
+        {value, StartTime, DocProps2} = lists:keytake(start_time, 1, DocProps1),
+        ?assertMatch({start_time, BinVal2} when is_binary(BinVal2), StartTime),
         ExpectedProps = [
             {database, ?DB},
             {doc_id, ?DOC1},
@@ -624,7 +637,7 @@ t_ejson_docs() ->
             {node, node()},
             {state, initializing}
         ],
-        ?assertEqual(ExpectedProps, lists:usort(DocProps))
+        ?assertEqual(ExpectedProps, lists:usort(DocProps2))
     end).
 
 
@@ -641,7 +654,7 @@ setup() ->
     meck:expect(couch_replicator_doc_processor_worker, spawn_worker, 3, wref),
     meck:expect(couch_replicator_scheduler, remove_job, 1, ok),
     meck:expect(couch_replicator_docs, remove_state_fields, 2, ok),
-    meck:expect(couch_replicator_docs, update_failed, 3, ok),
+    meck:expect(couch_replicator_docs, update_failed, 4, ok),
     {ok, Pid} = start_link(),
     Pid.
 
@@ -662,7 +675,7 @@ started_worker(Id) ->
 
 
 removed_job(Id) ->
-    meck:called(couch_replicator_scheduler, remove_job, [#rep{id = Id}]).
+    meck:called(couch_replicator_scheduler, remove_job, [test_rep(Id)]).
 
 
 did_not_remove_state_fields() ->
@@ -682,6 +695,10 @@ mock_existing_jobs_lookup(ExistingJobs) ->
             fun(?DB, ?DOC1) -> ExistingJobs end).
 
 
+test_rep(Id) ->
+  #rep{id = Id, start_time = {0, 0, 0}}.
+
+
 change() ->
     {[
         {<<"id">>, ?DOC1},

http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/blob/ab198aae/src/couch_replicator_doc_processor_worker.erl
----------------------------------------------------------------------
diff --git a/src/couch_replicator_doc_processor_worker.erl b/src/couch_replicator_doc_processor_worker.erl
index 6107438..a6bdeef 100644
--- a/src/couch_replicator_doc_processor_worker.erl
+++ b/src/couch_replicator_doc_processor_worker.erl
@@ -78,7 +78,8 @@ maybe_start_replication(Id, RepWithoutId) ->
         {temporary_error, Reason};
     {permanent_failure, Reason} ->
         {DbName, DocId} = Id,
-        couch_replicator_docs:update_failed(DbName, DocId, Reason),
+        StartTime = Rep#rep.start_time,
+        couch_replicator_docs:update_failed(DbName, DocId, Reason, StartTime),
         {permanent_failure, Reason}
     end.
 
@@ -199,7 +200,7 @@ setup() ->
     meck:expect(couch_replicator_scheduler, add_job, 1, ok),
     meck:expect(config, get, fun(_, _, Default) -> Default end),
     meck:expect(couch_server, get_uuid, 0, this_is_snek),
-    meck:expect(couch_replicator_docs, update_failed, 3, ok),
+    meck:expect(couch_replicator_docs, update_failed, 4, ok),
     meck:expect(couch_replicator_scheduler, rep_state, 1, nil),
     ok.
 

http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/blob/ab198aae/src/couch_replicator_docs.erl
----------------------------------------------------------------------
diff --git a/src/couch_replicator_docs.erl b/src/couch_replicator_docs.erl
index 71acc29..75889e9 100644
--- a/src/couch_replicator_docs.erl
+++ b/src/couch_replicator_docs.erl
@@ -19,8 +19,8 @@
 -export([ensure_cluster_rep_ddoc_exists/1]).
 -export([
     remove_state_fields/2,
-    update_doc_completed/3,
-    update_failed/3,
+    update_doc_completed/4,
+    update_failed/4,
     update_rep_id/1
 ]).
 
@@ -59,17 +59,19 @@ remove_state_fields(DbName, DocId) ->
         {<<"_replication_state_reason">>, undefined},
         {<<"_replication_stats">>, undefined}]).
 
--spec update_doc_completed(binary(), binary(), [_]) -> any().
-update_doc_completed(DbName, DocId, Stats) ->
+-spec update_doc_completed(binary(), binary(), [_], erlang:timestamp()) -> any().
+update_doc_completed(DbName, DocId, Stats, StartTime) ->
+    StartTimeBin = couch_replicator_utils:iso8601(StartTime),
     update_rep_doc(DbName, DocId, [
         {<<"_replication_state">>, <<"completed">>},
         {<<"_replication_state_reason">>, undefined},
+        {<<"_replication_start_time">>,  StartTimeBin},
         {<<"_replication_stats">>, {Stats}}]),
     couch_stats:increment_counter([couch_replicator, docs, completed_state_updates]).
 
 
--spec update_failed(binary(), binary(), any()) -> any().
-update_failed(DbName, DocId, Error) ->
+-spec update_failed(binary(), binary(), any(), erlang:timestamp()) -> any().
+update_failed(DbName, DocId, Error, StartTime) ->
     Reason = case Error of
         {bad_rep_doc, Reas} ->
             Reas;
@@ -77,8 +79,10 @@ update_failed(DbName, DocId, Error) ->
             to_binary(Error)
     end,
     couch_log:error("Error processing replication doc `~s`: ~s", [DocId, Reason]),
+    StartTimeBin = couch_replicator_utils:iso8601(StartTime),
     update_rep_doc(DbName, DocId, [
         {<<"_replication_state">>, <<"failed">>},
+        {<<"_replication_start_time">>, StartTimeBin},
         {<<"_replication_state_reason">>, Reason}]),
    couch_stats:increment_counter([couch_replicator, docs, failed_state_updates]).
 
@@ -295,9 +299,10 @@ update_rep_doc(RepDbName, #doc{body = {RepDocBody}} = RepDoc, KVs, _Try)
->
                     Body;
                 _ ->
                     Body1 = lists:keystore(K, 1, Body, KV),
+                    Timestamp = couch_replicator_utils:iso8601(os:timestamp()),
                     lists:keystore(
                         <<"_replication_state_time">>, 1, Body1,
-                        {<<"_replication_state_time">>, timestamp()})
+                        {<<"_replication_state_time">>, Timestamp})
                 end;
             ({K, _V} = KV, Body) ->
                 lists:keystore(K, 1, Body, KV)
@@ -329,28 +334,6 @@ save_rep_doc(DbName, Doc) ->
     end.
 
 
-% RFC3339 timestamps.
-% Note: doesn't include the time seconds fraction (RFC3339 says it's optional).
--spec timestamp() -> binary().
-timestamp() ->
-    {{Year, Month, Day}, {Hour, Min, Sec}} = calendar:now_to_local_time(os:timestamp()),
-    UTime = erlang:universaltime(),
-    LocalTime = calendar:universal_time_to_local_time(UTime),
-    DiffSecs = calendar:datetime_to_gregorian_seconds(LocalTime) -
-        calendar:datetime_to_gregorian_seconds(UTime),
-    zone(DiffSecs div 3600, (DiffSecs rem 3600) div 60),
-    iolist_to_binary(
-        io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w~s",
-            [Year, Month, Day, Hour, Min, Sec,
-                zone(DiffSecs div 3600, (DiffSecs rem 3600) div 60)])).
-
--spec zone(integer(), integer()) -> iolist().
-zone(Hr, Min) when Hr >= 0, Min >= 0 ->
-    io_lib:format("+~2..0w:~2..0w", [Hr, Min]);
-zone(Hr, Min) ->
-    io_lib:format("-~2..0w:~2..0w", [abs(Hr), abs(Min)]).
-
-
 -spec rep_user_ctx({[_]}) -> #user_ctx{}.
 rep_user_ctx({RepDoc}) ->
     case get_json_value(<<"user_ctx">>, RepDoc) of

http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/blob/ab198aae/src/couch_replicator_js_functions.hrl
----------------------------------------------------------------------
diff --git a/src/couch_replicator_js_functions.hrl b/src/couch_replicator_js_functions.hrl
index d70b1ff..3724b19 100644
--- a/src/couch_replicator_js_functions.hrl
+++ b/src/couch_replicator_js_functions.hrl
@@ -176,9 +176,15 @@
     function(doc) {
         state = doc._replication_state;
         if (state === 'failed') {
-            emit('failed', doc._replication_state_reason);
+            start_time = doc._replication_start_time;
+            last_updated = doc._replication_state_time;
+            state_reason = doc._replication_state_reason;
+            emit('failed', [start_time, last_updated, state_reason]);
         } else if (state === 'completed') {
-            emit('completed', doc._replication_stats);
+            start_time = doc._replication_start_time;
+            last_updated = doc._replication_state_time;
+            stats = doc._replication_stats;
+            emit('completed', [start_time, last_updated, stats]);
         }
     }
 ">>).

http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/blob/ab198aae/src/couch_replicator_scheduler.erl
----------------------------------------------------------------------
diff --git a/src/couch_replicator_scheduler.erl b/src/couch_replicator_scheduler.erl
index dadd8b0..0aa4ef6 100644
--- a/src/couch_replicator_scheduler.erl
+++ b/src/couch_replicator_scheduler.erl
@@ -124,7 +124,9 @@ job_summary(JobId, HealthThreshold) ->
                 {target, iolist_to_binary(ejson_url(Rep#rep.target))},
                 {state, State},
                 {info, Info},
-                {error_count, ErrorCount}
+                {error_count, ErrorCount},
+                {last_updated, last_updated(History)},
+                {start_time, couch_replicator_utils:iso8601(Rep#rep.start_time)}
             ];
         {error, not_found} ->
             nil  % Job might have just completed
@@ -748,20 +750,14 @@ job_ejson(Job) ->
     Rep = Job#job.rep,
     Source = ejson_url(Rep#rep.source),
     Target = ejson_url(Rep#rep.target),
-    History = lists:map(fun(Event) ->
-    EventProps  = case Event of
-        {{crashed, Reason}, _When} ->
-            [{type, crashed}, {reason, crash_reason_json(Reason)}];
-        {Type, _When} ->
-            [{type, Type}]
+    History = lists:map(fun({Type, When}) ->
+        EventProps  = case Type of
+            {crashed, Reason} ->
+                [{type, crashed}, {reason, crash_reason_json(Reason)}];
+            Type ->
+                [{type, Type}]
         end,
-        {_Type, {_Mega, _Sec, Micros}=When} = Event,
-        {{Y, Mon, D}, {H, Min, S}} = calendar:now_to_universal_time(When),
-        ISO8601 = iolist_to_binary(io_lib:format(
-            "~B-~2..0B-~2..0BT~2..0B-~2..0B-~2..0B.~BZ",
-            [Y,Mon,D,H,Min,S,Micros]
-        )),
-        {[{timestamp, ISO8601} | EventProps]}
+        {[{timestamp, couch_replicator_utils:iso8601(When)} | EventProps]}
     end, Job#job.history),
     {BaseID, Ext} = Job#job.id,
     Pid = case Job#job.pid of
@@ -779,7 +775,8 @@ job_ejson(Job) ->
         {user, (Rep#rep.user_ctx)#user_ctx.name},
         {doc_id, Rep#rep.doc_id},
         {history, History},
-        {node, node()}
+        {node, node()},
+        {start_time, couch_replicator_utils:iso8601(Rep#rep.start_time)}
     ]}.
 
 
@@ -808,6 +805,11 @@ crash_reason_json(Error) ->
     couch_replicator_utils:rep_error_to_binary(Error).
 
 
+-spec last_updated([_]) -> binary().
+last_updated([{_Type, When} | _]) ->
+    couch_replicator_utils:iso8601(When).
+
+
 -spec is_continuous(#job{}) -> boolean().
 is_continuous(#job{rep = Rep}) ->
     couch_util:get_value(continuous, Rep#rep.options, false).

http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/blob/ab198aae/src/couch_replicator_scheduler_job.erl
----------------------------------------------------------------------
diff --git a/src/couch_replicator_scheduler_job.erl b/src/couch_replicator_scheduler_job.erl
index cdedc39..9d14a9d 100644
--- a/src/couch_replicator_scheduler_job.erl
+++ b/src/couch_replicator_scheduler_job.erl
@@ -494,8 +494,9 @@ doc_update_triggered(#rep{id = RepId, doc_id = DocId}) ->
 -spec doc_update_completed(#rep{}, list()) -> ok.
 doc_update_completed(#rep{db_name = null}, _Stats) ->
     ok;
-doc_update_completed(#rep{id = RepId, doc_id = DocId, db_name = DbName}, Stats) ->
-    couch_replicator_docs:update_doc_completed(DbName, DocId, Stats),
+doc_update_completed(#rep{id = RepId, doc_id = DocId, db_name = DbName,
+    start_time = StartTime}, Stats) ->
+    couch_replicator_docs:update_doc_completed(DbName, DocId, Stats, StartTime),
     couch_log:notice("Replication `~s` completed (triggered by `~s`)",
         [pp_rep_id(RepId), DocId]),
     ok.
@@ -538,7 +539,8 @@ init_state(Rep) ->
         id = {BaseId, _Ext},
         source = Src0, target = Tgt,
         options = Options, user_ctx = UserCtx,
-        type = Type, view = View
+        type = Type, view = View,
+        start_time = StartTime
     } = Rep,
     % Adjust minimum number of http source connections to 2 to avoid deadlock
     Src = adjust_maxconn(Src0, BaseId),
@@ -571,7 +573,7 @@ init_state(Rep) ->
         committed_seq = StartSeq,
         source_log = SourceLog,
         target_log = TargetLog,
-        rep_starttime = httpd_util:rfc1123_date(),
+        rep_starttime = StartTime,
         src_starttime = get_value(<<"instance_start_time">>, SourceInfo),
         tgt_starttime = get_value(<<"instance_start_time">>, TargetInfo),
         session_id = couch_uuids:random(),
@@ -673,7 +675,8 @@ do_checkpoint(State) ->
     {SrcInstanceStartTime, TgtInstanceStartTime} ->
         couch_log:notice("recording a checkpoint for `~s` -> `~s` at source update_seq
~p",
             [SourceName, TargetName, NewSeq]),
-        StartTime = ?l2b(ReplicationStartTime),
+        UniversalStartTime = calendar:now_to_universal_time(ReplicationStartTime),
+        StartTime = ?l2b(httpd_util:rfc1123_date(UniversalStartTime)),
         EndTime = ?l2b(httpd_util:rfc1123_date()),
         NewHistoryEntry = {[
             {<<"session_id">>, SessionId},

http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/blob/ab198aae/src/couch_replicator_utils.erl
----------------------------------------------------------------------
diff --git a/src/couch_replicator_utils.erl b/src/couch_replicator_utils.erl
index e2f5ef8..4ab4f81 100644
--- a/src/couch_replicator_utils.erl
+++ b/src/couch_replicator_utils.erl
@@ -20,6 +20,7 @@
 -export([rep_error_to_binary/1]).
 -export([get_json_value/2, get_json_value/3]).
 -export([pp_rep_id/1]).
+-export([iso8601/1]).
 
 -export([handle_db_event/3]).
 
@@ -135,3 +136,10 @@ sum_stats(S1, S2) ->
 
 parse_rep_doc(Props, UserCtx) ->
     couch_replicator_docs:parse_rep_doc(Props, UserCtx).
+
+
+-spec iso8601(erlang:timestamp()) -> binary().
+iso8601({_Mega, _Sec, _Micro} = Timestamp) ->
+    {{Y, Mon, D}, {H, Min, S}} = calendar:now_to_universal_time(Timestamp),
+    Format = "~B-~2..0B-~2..0BT~2..0B-~2..0B-~2..0BZ",
+    iolist_to_binary(io_lib:format(Format, [Y, Mon, D, H, Min, S])).


Mime
View raw message