public static async Task UploadResult(ArtilleryResult artilleryResult, string serverurl, string username, string password, Dictionary <string, string> extraFields)
    {
        var allrequests = new List <ElasticBulkDocument>();

        foreach (var request in artilleryResult.Requests)
        {
            dynamic jobject = new JObject
            {
                ["LoadtestID"]       = artilleryResult.LoadtestID,
                ["@timestamp"]       = request.StartTime.ToString("yyyy-MM-ddTHH:mm:ss.fff"),
                ["LatencyNS"]        = request.Latency,
                ["HttpResult"]       = request.HttpResult,
                ["RebasedTimestamp"] = request.StartTime.AddMilliseconds(artilleryResult.Diff_ms).ToString("yyyy-MM-ddTHH:mm:ss.fff")
            };

            foreach (var field in extraFields)
            {
                jobject[field.Key] = field.Value;
            }

            var bulkDocument = new ElasticBulkDocument
            {
                Index    = $"artillery-{request.StartTime:yyyy}.{request.StartTime:MM}",
                Id       = GetHashString(jobject.ToString()),
                Document = jobject
            };

            allrequests.Add(bulkDocument);
        }

        Log($"Request count: {allrequests.Count}");

        await Elastic.PutIntoIndex(serverurl, username, password, allrequests.ToArray());
    }
    public static async Task CopyDocuments(
        ElasticCopySettings source,
        string targetServerurl, string targetUsername, string targetPassword, string targetIndex,
        DateTime starttime, DateTime endtime, long diff_ms,
        Dictionary <string, string> extraFields)
    {
        Log($"Copying: {targetServerurl}/{targetUsername}/{new string('*', targetPassword.Length)}/{targetIndex ?? "<null>"}, " +
            $"starttime: {starttime:yyyy-MM-dd HH:mm:ss.fff}, endtime: {endtime:yyyy-MM-dd HH:mm:ss.fff}, diffms: {diff_ms}");

        string timestampfieldname = source.TimestampField;

        var newDocuments = new List <ElasticBulkDocument>();

        for (DateTime spanStart = starttime; spanStart < endtime; spanStart = spanStart.AddMinutes(2))
        {
            DateTime spanEnd = spanStart.AddMinutes(2) > endtime ? endtime : spanStart.AddMinutes(2);

            Log($"time span: {spanStart:yyyy-MM-dd HH:mm:ss.fff} - {spanEnd:yyyy-MM-dd HH:mm:ss.fff}");

            dynamic sourceDocuments = await Elastic.GetRowsAsync(source.SourceServerurl, source.SourceUsername, source.SourcePassword, source.SourceIndex,
                                                                 source.ElasticFilterField, source.ElasticFilterValue,
                                                                 timestampfieldname, spanStart, spanEnd);

            if (sourceDocuments == null || sourceDocuments.Count == 0)
            {
                Log("Got no documents.");
                continue;
            }
            Log($"Source document count: {sourceDocuments.Count}");

            foreach (var sourceDocument in sourceDocuments)
            {
                dynamic jobject = new JObject(sourceDocument._source);

                DateTime timestamp = jobject[timestampfieldname];
                jobject[$"Rebased{timestampfieldname}"] = timestamp.AddMilliseconds(diff_ms).ToString("o");

                foreach (var field in extraFields)
                {
                    jobject[field.Key] = field.Value;
                }

                var bulkDocument = new ElasticBulkDocument
                {
                    Index    = GetIndexWithDate(targetIndex, timestamp) ?? sourceDocument._index,
                    Id       = sourceDocument._id,
                    Document = jobject
                };

                newDocuments.Add(bulkDocument);
            }
        }

        if (newDocuments.Count == 0)
        {
            Log("No documents to copy.");
            return;
        }

        MakeSureDoublesAreDoubles(newDocuments.Select(d => d.Document).ToArray());

        Log($"New document count: {newDocuments.Count}");

        await Elastic.PutIntoIndex(targetServerurl, targetUsername, targetPassword, newDocuments.ToArray());
    }