James Allen 10 лет назад
Родитель
Сommit
e5cf856ddf
1 измененных файлов с 152 добавлено и 0 удалено
  1. 152 0
      services/document-updater/test/stress/coffee/run.coffee

+ 152 - 0
services/document-updater/test/stress/coffee/run.coffee

@@ -0,0 +1,152 @@
+DocUpdaterClient = require "../../acceptance/js/helpers/DocUpdaterClient"
+# MockTrackChangesApi = require "../../acceptance/js/helpers/MockTrackChangesApi"
+# MockWebApi = require "../../acceptance/js/helpers/MockWebApi"
+assert = require "assert"
+async = require "async"
+
+insert = (string, pos, content) ->
+	string.slice(0, pos) + content + string.slice(pos)
+
+transform = (op1, op2) ->
+	if op2.p < op1.p
+		op1.p += op2.i.length
+	return op1
+
+class StressTestClient
+	constructor: (@options = {}) ->
+		@options.updateDelay ?= 200
+		@project_id = @options.project_id or DocUpdaterClient.randomId()
+		@doc_id = @options.doc_id or DocUpdaterClient.randomId()
+		@pos = @options.pos or 0
+		@content = @options.content or ""
+
+		@client_id = DocUpdaterClient.randomId()
+		@version = @options.version or 0
+		@inflight_op = null
+		@charCode = 0
+		
+		@counts = {
+			conflicts: 0
+			local_updates: 0
+			remote_updates: 0
+			max_delay: 0
+		}
+		
+		DocUpdaterClient.subscribeToAppliedOps (channel, update) =>
+			update = JSON.parse(update)
+			if update.doc_id == @doc_id
+				@processReply(update)
+	
+	sendUpdate: () ->
+		data = String.fromCharCode(65 + @charCode++ % 26)
+		@content = insert(@content, @pos, data)
+		DocUpdaterClient.sendUpdate(
+			@project_id, @doc_id
+			{
+				doc: @doc_id
+				op: [@inflight_op = {
+					i: data
+					p: @pos++
+				}]
+				v: @version
+				meta:
+					source: @client_id
+			}
+		)
+		@inflight_op_sent = Date.now()
+	
+	processReply: (update) ->
+		if update.error?
+			throw new Error("Error from server: '#{update.error}'")
+		assert(update.op.v == @version, "Op version from server is not increasing by 1 each time")
+		@version++
+		if update.op.meta.source == @client_id
+			@counts.local_updates++
+			@inflight_op = null
+			delay = Date.now() - @inflight_op_sent
+			@counts.max_delay = Math.max(@counts.max_delay, delay)
+			@continue()
+		else
+			assert(update.op.op.length == 1)
+			@counts.remote_updates++
+			external_op = update.op.op[0]
+			if @inflight_op?
+				@counts.conflicts++
+				external_op = transform(external_op, @inflight_op)
+			if external_op.p < @pos
+				@pos += external_op.i.length
+			@content = insert(@content, external_op.p, external_op.i)
+	
+	continue: () ->
+		if @updateCount > 0
+			@updateCount--
+			setTimeout () =>
+				@sendUpdate()
+			, @options.updateDelay * ( 0.5 + Math.random() )
+		else
+			@updateCallback()
+	
+	runForNUpdates: (n, callback = (error) ->) ->
+		@updateCallback = callback
+		@updateCount = n
+		@continue()
+	
+	check: (callback = (error) ->) ->
+		DocUpdaterClient.getDoc @project_id, @doc_id, (error, res, body) =>
+			throw error if error?
+			if !body.lines?
+				return console.error "[#{new Date()}] ERROR: Invalid response from get doc (#{doc_id})", body
+			content = body.lines.join("\n")
+			if content != @content
+				console.error "[#{new Date()}] Error: Client content does not match server (Server: '#{content}', Client: '#{@content}')"
+			# TODO: Check content is of the correct form
+			callback()
+
+
+checkDocument = (project_id, doc_id, clients, callback = (error) ->) ->
+	jobs = clients.map (client) ->
+		(cb) -> client.check cb
+	async.parallel jobs, callback
+
+printSummary = (doc_id, clients) ->
+	slot = require('cluster-key-slot')
+	now = new Date()
+	console.log "[#{now}] [#{doc_id.slice(0,4)} (slot: #{slot(doc_id)})] #{clients.length} clients..."
+	for client in clients
+		console.log "[#{now}] \t[#{client.client_id.slice(0,4)}] { local: #{client.counts.local_updates }, remote: #{client.counts.remote_updates}, conflicts: #{client.counts.conflicts}, max_delay: #{client.counts.max_delay} }"
+		client.counts = {
+			local_updates: 0
+			remote_updates: 0
+			conflicts: 0
+			max_delay: 0
+		}
+
+UPDATE_DELAY = parseInt(process.argv[2], 10)
+SAMPLE_INTERVAL = parseInt(process.argv[3], 10)
+
+for doc_and_project_id in process.argv.slice(4)
+	do (doc_and_project_id) ->
+		[project_id, doc_id] = doc_and_project_id.split(":")
+		console.log {project_id, doc_id}
+		DocUpdaterClient.getDoc project_id, doc_id, (error, res, body) =>
+			throw error if error?
+			if !body.lines?
+				return console.error "[#{new Date()}] ERROR: Invalid response from get doc (#{doc_id})", body
+			content = body.lines.join("\n")
+			version = body.version
+		
+			clients = []
+			for pos in [1, 2, 3, 4, 5]
+				do (pos) ->
+					client = new StressTestClient({doc_id, project_id, content, pos: pos, version: version, updateDelay: UPDATE_DELAY})
+					clients.push client
+			
+			do runBatch = () ->
+				jobs = clients.map (client) ->
+					(cb) -> client.runForNUpdates(SAMPLE_INTERVAL / UPDATE_DELAY, cb)
+				async.parallel jobs, (error) ->
+					throw error if error?
+					printSummary(doc_id, clients)
+					checkDocument project_id, doc_id, clients, (error) ->
+						throw error if error?
+						runBatch()