From 988c5f2939764fc5ef2a8fe59ec82fa75034d1ce Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Fri, 2 Feb 2024 13:14:30 -0800 Subject: [PATCH 01/29] init --- ...ControllerAsyncRPCHandlerInitializer.scala | 1 + .../promisehandlers/ForLoopHandler.scala | 19 +++++++ .../architecture/worker/DataProcessor.scala | 10 ++++ .../DataProcessorRPCHandlerInitializer.scala | 1 + .../promisehandlers/ResumeLoopHandler.scala | 24 +++++++++ .../workflow/common/operators/LogicalOp.scala | 3 ++ .../common/operators/OperatorExecutor.scala | 4 ++ .../operators/loop/LoopEndOpDesc.scala | 48 ++++++++++++++++++ .../operators/loop/LoopEndOpExec.scala | 32 ++++++++++++ .../operators/loop/LoopStartOpDesc.scala | 50 +++++++++++++++++++ .../operators/loop/LoopStartOpExec.scala | 43 ++++++++++++++++ 11 files changed, 235 insertions(+) create mode 100644 core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/ForLoopHandler.scala create mode 100644 core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala create mode 100644 core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala create mode 100644 core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala create mode 100644 core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala create mode 100644 core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/ControllerAsyncRPCHandlerInitializer.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/ControllerAsyncRPCHandlerInitializer.scala index 837c4b79630..951463e6327 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/ControllerAsyncRPCHandlerInitializer.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/ControllerAsyncRPCHandlerInitializer.scala @@ -12,6 +12,7 @@ class ControllerAsyncRPCHandlerInitializer( with LinkWorkersHandler with AssignBreakpointHandler with WorkerExecutionCompletedHandler + with ForLoopHandler with WorkerExecutionStartedHandler with LocalBreakpointTriggeredHandler with PauseHandler diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/ForLoopHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/ForLoopHandler.scala new file mode 100644 index 00000000000..ad8179976a2 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/ForLoopHandler.scala @@ -0,0 +1,19 @@ +package edu.uci.ics.amber.engine.architecture.controller.promisehandlers + +import edu.uci.ics.amber.engine.architecture.controller.ControllerAsyncRPCHandlerInitializer +import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.ForLoopHandler.IterationCompleted +import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.ResumeLoopHandler.ResumeLoop +import edu.uci.ics.amber.engine.common.rpc.AsyncRPCServer.ControlCommand +import edu.uci.ics.amber.engine.common.virtualidentity.ActorVirtualIdentity + +object ForLoopHandler { + final case class IterationCompleted(workerId: ActorVirtualIdentity) extends ControlCommand[Unit] +} + +trait ForLoopHandler { + this: ControllerAsyncRPCHandlerInitializer => + + registerHandler { (msg: IterationCompleted, _) => + send(ResumeLoop(), msg.workerId) + } +} diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index 68a02ce197d..743194b1d06 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -3,6 +3,7 @@ package edu.uci.ics.amber.engine.architecture.worker import com.softwaremill.macwire.wire import edu.uci.ics.amber.engine.architecture.common.AmberProcessor import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.ConsoleMessageHandler.ConsoleMessageTriggered +import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.ForLoopHandler.IterationCompleted import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.LinkCompletedHandler.LinkCompleted import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.WorkerExecutionCompletedHandler.WorkerExecutionCompleted import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.WorkerExecutionStartedHandler.WorkerStateUpdated @@ -16,6 +17,7 @@ import edu.uci.ics.amber.engine.architecture.messaginglayer.{OutputManager, Work import edu.uci.ics.amber.engine.architecture.scheduling.config.OperatorConfig import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{ DPOutputIterator, + EndOfIteration, FinalizeLink, FinalizeOperator } @@ -44,6 +46,7 @@ import edu.uci.ics.amber.engine.common.{ VirtualIdentityUtils } import edu.uci.ics.amber.error.ErrorUtils.{mkConsoleMessage, safely} +import edu.uci.ics.texera.workflow.operators.loop.LoopStartOpExec import scala.collection.mutable @@ -60,6 +63,7 @@ object DataProcessor { } case class FinalizeLink(link: PhysicalLink) extends SpecialDataTuple case class FinalizeOperator() extends SpecialDataTuple + case class EndOfIteration(workerId: ActorVirtualIdentity) extends SpecialDataTuple class DPOutputIterator extends Iterator[(ITuple, Option[PortIdentity])] { val queue = new mutable.Queue[(ITuple, Option[PortIdentity])] @@ -261,6 +265,8 @@ class DataProcessor( if (outputTuple == null) return outputTuple match { + case EndOfIteration(workerId) => + asyncRPCClient.send(IterationCompleted(workerId), CONTROLLER) case FinalizeOperator() => outputManager.emitEndOfUpstream() // Send Completed signal to worker actor. @@ -341,6 +347,10 @@ class DataProcessor( initBatch(channelId, tuples) processInputTuple(Left(inputBatch(currentInputIdx))) case EndOfUpstream() => + if (operator.isInstanceOf[LoopStartOpExec]) { + processInputTuple(Right(InputExhausted())) + return + } val currentLink = upstreamLinkStatus.getInputLink(channelId.fromWorkerId) upstreamLinkStatus.markWorkerEOF(channelId.fromWorkerId) if (upstreamLinkStatus.isLinkEOF(currentLink)) { diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorRPCHandlerInitializer.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorRPCHandlerInitializer.scala index 83a9f14fdef..2bf00ee02ca 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorRPCHandlerInitializer.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorRPCHandlerInitializer.scala @@ -15,6 +15,7 @@ class DataProcessorRPCHandlerInitializer(val dp: DataProcessor) with QueryCurrentInputTupleHandler with QueryStatisticsHandler with ResumeHandler + with ResumeLoopHandler with StartHandler with UpdateInputLinkingHandler with AssignLocalBreakpointHandler diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala new file mode 100644 index 00000000000..30de9096133 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala @@ -0,0 +1,24 @@ +package edu.uci.ics.amber.engine.architecture.worker.promisehandlers + +import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.ResumeLoopHandler.ResumeLoop +import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer +import edu.uci.ics.amber.engine.common.ambermessage.{DataFrame, EndOfUpstream} +import edu.uci.ics.amber.engine.common.rpc.AsyncRPCServer.ControlCommand +import edu.uci.ics.texera.workflow.operators.loop.LoopStartOpExec + +object ResumeLoopHandler { + final case class ResumeLoop() extends ControlCommand[Unit] +} + +trait ResumeLoopHandler { + this: DataProcessorRPCHandlerInitializer => + registerHandler { (_: ResumeLoop, _) => + { + dp.processDataPayload( + dp.currentBatchChannel, + DataFrame(dp.operator.asInstanceOf[LoopStartOpExec].buffer.toArray) + ) + dp.processDataPayload(dp.currentBatchChannel, EndOfUpstream()) + } + } +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala index 4c8a22ab9f5..5d00dd714e0 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala @@ -28,6 +28,7 @@ import edu.uci.ics.texera.workflow.operators.intervalJoin.IntervalJoinOpDesc import edu.uci.ics.texera.workflow.operators.keywordSearch.KeywordSearchOpDesc import edu.uci.ics.texera.workflow.operators.limit.LimitOpDesc import edu.uci.ics.texera.workflow.operators.linearregression.LinearRegressionOpDesc +import edu.uci.ics.texera.workflow.operators.loop.{LoopEndOpDesc, LoopStartOpDesc} import edu.uci.ics.texera.workflow.operators.projection.ProjectionOpDesc import edu.uci.ics.texera.workflow.operators.randomksampling.RandomKSamplingOpDesc import edu.uci.ics.texera.workflow.operators.regex.RegexOpDesc @@ -137,6 +138,8 @@ trait StateTransferFunc new Type(value = classOf[AsterixDBSourceOpDesc], name = "AsterixDBSource"), new Type(value = classOf[TypeCastingOpDesc], name = "TypeCasting"), new Type(value = classOf[LimitOpDesc], name = "Limit"), + new Type(value = classOf[LoopStartOpDesc], name = "LoopStart"), + new Type(value = classOf[LoopEndOpDesc], name = "LoopEnd"), new Type(value = classOf[RandomKSamplingOpDesc], name = "RandomKSampling"), new Type(value = classOf[ReservoirSamplingOpDesc], name = "ReservoirSampling"), new Type(value = classOf[HashJoinOpDesc[String]], name = "HashJoin"), diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/OperatorExecutor.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/OperatorExecutor.scala index 0e6284ef5fc..79e096fcc2e 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/OperatorExecutor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/OperatorExecutor.scala @@ -1,5 +1,6 @@ package edu.uci.ics.texera.workflow.common.operators +import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.EndOfIteration import edu.uci.ics.amber.engine.architecture.worker.PauseManager import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient import edu.uci.ics.amber.engine.common.{IOperatorExecutor, InputExhausted} @@ -15,6 +16,9 @@ trait OperatorExecutor extends IOperatorExecutor { pauseManager: PauseManager, asyncRPCClient: AsyncRPCClient ): Iterator[(ITuple, Option[PortIdentity])] = { + if (tuple.isLeft && tuple.left.get.isInstanceOf[EndOfIteration]) { + return Iterator((tuple.left.get, Option.empty)) + } processTexeraTuple( tuple.asInstanceOf[Either[Tuple, InputExhausted]], input, diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala new file mode 100644 index 00000000000..e74feea3300 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala @@ -0,0 +1,48 @@ +package edu.uci.ics.texera.workflow.operators.loop + +import com.fasterxml.jackson.annotation.{JsonProperty, JsonPropertyDescription} +import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle +import edu.uci.ics.amber.engine.architecture.deploysemantics.PhysicalOp +import edu.uci.ics.amber.engine.architecture.deploysemantics.layer.OpExecInitInfo +import edu.uci.ics.amber.engine.common.virtualidentity.{ExecutionIdentity, WorkflowIdentity} +import edu.uci.ics.amber.engine.common.workflow.{InputPort, OutputPort} +import edu.uci.ics.texera.workflow.common.metadata.{OperatorGroupConstants, OperatorInfo} +import edu.uci.ics.texera.workflow.common.operators.LogicalOp +import edu.uci.ics.texera.workflow.common.tuple.schema.Schema + +class LoopEndOpDesc extends LogicalOp { + + @JsonProperty(required = true) + @JsonSchemaTitle("Iteration") + @JsonPropertyDescription("the max number of iterations") + var i: Int = _ + + override def getPhysicalOp( + workflowId: WorkflowIdentity, + executionId: ExecutionIdentity + ): PhysicalOp = { + PhysicalOp + .oneToOnePhysicalOp( + workflowId, + executionId, + operatorIdentifier, + OpExecInitInfo((_, _, _) => new LoopEndOpExec(i)) + ) + .withInputPorts(operatorInfo.inputPorts, inputPortToSchemaMapping) + .withOutputPorts(operatorInfo.outputPorts, outputPortToSchemaMapping) + .withSuggestedWorkerNum(1) + } + + override def operatorInfo: OperatorInfo = + OperatorInfo( + "LoopEnd", + "Limit the number of output rows", + OperatorGroupConstants.UTILITY_GROUP, + inputPorts = List(InputPort()), + outputPorts = List(OutputPort()), + supportReconfiguration = true + ) + + override def getOutputSchema(schemas: Array[Schema]): Schema = schemas(0) + +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala new file mode 100644 index 00000000000..4063ff0bfcd --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala @@ -0,0 +1,32 @@ +package edu.uci.ics.texera.workflow.operators.loop + +import edu.uci.ics.amber.engine.architecture.worker.PauseManager +import edu.uci.ics.amber.engine.common.InputExhausted +import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient +import edu.uci.ics.texera.workflow.common.operators.OperatorExecutor +import edu.uci.ics.texera.workflow.common.tuple.Tuple + +import scala.collection.mutable + +class LoopEndOpExec(val iteration: Int) extends OperatorExecutor { + var i = 0 + var buffer = new mutable.ArrayBuffer[Tuple] + + override def open(): Unit = {} + + override def close(): Unit = {} + + override def processTexeraTuple( + tuple: Either[Tuple, InputExhausted], + input: Int, + pauseManager: PauseManager, + asyncRPCClient: AsyncRPCClient + ): Iterator[Tuple] = { + tuple match { + case Left(t) => + buffer.append(t) + Iterator() + case Right(_) => buffer.iterator + } + } +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala new file mode 100644 index 00000000000..40bd03f2846 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala @@ -0,0 +1,50 @@ +package edu.uci.ics.texera.workflow.operators.loop + +import com.fasterxml.jackson.annotation.{JsonProperty, JsonPropertyDescription} +import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle +import edu.uci.ics.amber.engine.architecture.deploysemantics.PhysicalOp +import edu.uci.ics.amber.engine.architecture.deploysemantics.layer.OpExecInitInfo +import edu.uci.ics.amber.engine.common.virtualidentity.{ExecutionIdentity, WorkflowIdentity} +import edu.uci.ics.amber.engine.common.workflow.{InputPort, OutputPort} +import edu.uci.ics.texera.workflow.common.metadata.{OperatorGroupConstants, OperatorInfo} +import edu.uci.ics.texera.workflow.common.operators.LogicalOp +import edu.uci.ics.texera.workflow.common.tuple.schema.Schema + +class LoopStartOpDesc extends LogicalOp { + + @JsonProperty(required = true) + @JsonSchemaTitle("Iteration") + @JsonPropertyDescription("the max number of iterations") + var i: Int = _ + + override def getPhysicalOp( + workflowId: WorkflowIdentity, + executionId: ExecutionIdentity + ): PhysicalOp = { + PhysicalOp + .oneToOnePhysicalOp( + workflowId, + executionId, + operatorIdentifier, + OpExecInitInfo((_, _, operatorConfig) => { + new LoopStartOpExec(operatorConfig.workerConfigs.head.workerId) + }) + ) + .withInputPorts(operatorInfo.inputPorts, inputPortToSchemaMapping) + .withOutputPorts(operatorInfo.outputPorts, outputPortToSchemaMapping) + .withSuggestedWorkerNum(1) + } + + override def operatorInfo: OperatorInfo = + OperatorInfo( + "LoopStart", + "Limit the number of output rows", + OperatorGroupConstants.UTILITY_GROUP, + inputPorts = List(InputPort()), + outputPorts = List(OutputPort()), + supportReconfiguration = true + ) + + override def getOutputSchema(schemas: Array[Schema]): Schema = schemas(0) + +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala new file mode 100644 index 00000000000..486360071a8 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala @@ -0,0 +1,43 @@ +package edu.uci.ics.texera.workflow.operators.loop + +import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.EndOfIteration +import edu.uci.ics.amber.engine.architecture.worker.PauseManager +import edu.uci.ics.amber.engine.common.InputExhausted +import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient +import edu.uci.ics.amber.engine.common.tuple.ITuple +import edu.uci.ics.amber.engine.common.virtualidentity.ActorVirtualIdentity +import edu.uci.ics.amber.engine.common.workflow.PortIdentity +import edu.uci.ics.texera.workflow.common.operators.OperatorExecutor +import edu.uci.ics.texera.workflow.common.tuple.Tuple + +import scala.collection.mutable + +class LoopStartOpExec(val workerId: ActorVirtualIdentity) extends OperatorExecutor { + var count = 0 + var buffer = new mutable.ArrayBuffer[ITuple] + + override def processTuple( + tuple: Either[ITuple, InputExhausted], + input: Int, + pauseManager: PauseManager, + asyncRPCClient: AsyncRPCClient + ): Iterator[(ITuple, Option[PortIdentity])] = { + tuple match { + case Left(t) => + buffer.append(t) + Iterator((t, None)) + case Right(_) => Iterator((EndOfIteration(workerId), None)) + } + } + + override def open(): Unit = {} + + override def close(): Unit = {} + + override def processTexeraTuple( + tuple: Either[Tuple, InputExhausted], + input: Int, + pauseManager: PauseManager, + asyncRPCClient: AsyncRPCClient + ): Iterator[Tuple] = ??? +} From e4d4c1aecb31a98393bed304492f8f576bc18b77 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Fri, 2 Feb 2024 17:37:46 -0800 Subject: [PATCH 02/29] update --- .../architecture/worker/statistics.proto | 1 + .../uci/ics/texera/workflowruntimestate.proto | 1 + .../controller/OperatorExecution.scala | 5 +- .../architecture/worker/DataProcessor.scala | 63 ++++++++---------- .../promisehandlers/OpenOperatorHandler.scala | 12 ++++ .../promisehandlers/ResumeLoopHandler.scala | 21 ++++-- .../event/OperatorStatisticsUpdateEvent.scala | 3 +- .../web/service/ExecutionStatsService.scala | 3 +- .../operators/loop/LoopStartOpDesc.scala | 2 +- .../operators/loop/LoopStartOpExec.scala | 16 +++-- .../worker/statistics/StatisticsProto.scala | 6 +- .../worker/statistics/WorkerStatistics.scala | 42 ++++++++++-- .../OperatorRuntimeStats.scala | 42 ++++++++++-- .../WorkflowruntimestateProto.scala | 43 ++++++------ .../src/assets/operator_images/LoopEnd.png | Bin 0 -> 5865 bytes .../src/assets/operator_images/LoopStart.png | Bin 0 -> 2138 bytes 16 files changed, 174 insertions(+), 86 deletions(-) create mode 100644 core/new-gui/src/assets/operator_images/LoopEnd.png create mode 100644 core/new-gui/src/assets/operator_images/LoopStart.png diff --git a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/worker/statistics.proto b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/worker/statistics.proto index d11f1afb970..3615b9926d2 100644 --- a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/worker/statistics.proto +++ b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/worker/statistics.proto @@ -27,4 +27,5 @@ message WorkerStatistics { int64 data_processing_time = 4; int64 control_processing_time = 5; int64 idle_time = 6; + int64 loop_i = 7; } diff --git a/core/amber/src/main/protobuf/edu/uci/ics/texera/workflowruntimestate.proto b/core/amber/src/main/protobuf/edu/uci/ics/texera/workflowruntimestate.proto index bbbf1046e74..8f6ff93c52d 100644 --- a/core/amber/src/main/protobuf/edu/uci/ics/texera/workflowruntimestate.proto +++ b/core/amber/src/main/protobuf/edu/uci/ics/texera/workflowruntimestate.proto @@ -78,6 +78,7 @@ message OperatorRuntimeStats{ int64 data_processing_time = 5; int64 control_processing_time = 6; int64 idle_time = 7; + int64 loop_i = 8; } message ExecutionStatsStore { diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/OperatorExecution.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/OperatorExecution.scala index 3d8c9d2b96c..84dc678a591 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/OperatorExecution.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/OperatorExecution.scala @@ -46,7 +46,7 @@ class OperatorExecution( WorkerInfo( id, UNINITIALIZED, - WorkerStatistics(UNINITIALIZED, 0, 0, 0, 0, 0), + WorkerStatistics(UNINITIALIZED, 0, 0, 0, 0, 0, 0), mutable.HashSet(ChannelIdentity(CONTROLLER, id, isControl = true)) ) ) @@ -122,6 +122,7 @@ class OperatorExecution( numWorkers, getDataProcessingTime, getControlProcessingTime, - getIdleTime + getIdleTime, + statistics.map(_.loopI).sum ) } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index 743194b1d06..ea4cad2d686 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -8,45 +8,24 @@ import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.LinkComp import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.WorkerExecutionCompletedHandler.WorkerExecutionCompleted import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.WorkerExecutionStartedHandler.WorkerStateUpdated import edu.uci.ics.amber.engine.architecture.deploysemantics.PhysicalOp -import edu.uci.ics.amber.engine.architecture.deploysemantics.layer.{ - OpExecInitInfoWithCode, - OpExecInitInfoWithFunc -} +import edu.uci.ics.amber.engine.architecture.deploysemantics.layer.{OpExecInitInfoWithCode, OpExecInitInfoWithFunc} import edu.uci.ics.amber.engine.architecture.logreplay.ReplayLogManager import edu.uci.ics.amber.engine.architecture.messaginglayer.{OutputManager, WorkerTimerService} import edu.uci.ics.amber.engine.architecture.scheduling.config.OperatorConfig -import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{ - DPOutputIterator, - EndOfIteration, - FinalizeLink, - FinalizeOperator -} +import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{DPOutputIterator, EndOfIteration, FinalizeLink, FinalizeOperator} import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.PauseHandler.PauseWorker -import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState.{ - COMPLETED, - PAUSED, - READY, - RUNNING -} +import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.ResumeLoopHandler +import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState.{COMPLETED, PAUSED, READY, RUNNING} import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerStatistics import edu.uci.ics.amber.engine.common.ambermessage._ import edu.uci.ics.amber.engine.common.statetransition.WorkerStateManager import edu.uci.ics.amber.engine.common.tuple.ITuple import edu.uci.ics.amber.engine.common.virtualidentity.util.{CONTROLLER, SELF, SOURCE_STARTER_OP} -import edu.uci.ics.amber.engine.common.virtualidentity.{ - ActorVirtualIdentity, - ChannelIdentity, - PhysicalOpIdentity -} +import edu.uci.ics.amber.engine.common.virtualidentity.{ActorVirtualIdentity, ChannelIdentity, PhysicalOpIdentity} import edu.uci.ics.amber.engine.common.workflow.{PhysicalLink, PortIdentity} -import edu.uci.ics.amber.engine.common.{ - IOperatorExecutor, - ISinkOperatorExecutor, - InputExhausted, - VirtualIdentityUtils -} +import edu.uci.ics.amber.engine.common.{IOperatorExecutor, ISinkOperatorExecutor, InputExhausted, VirtualIdentityUtils} import edu.uci.ics.amber.error.ErrorUtils.{mkConsoleMessage, safely} -import edu.uci.ics.texera.workflow.operators.loop.LoopStartOpExec +import edu.uci.ics.texera.workflow.operators.loop.{LoopEndOpExec, LoopStartOpExec} import scala.collection.mutable @@ -125,7 +104,11 @@ class DataProcessor( PhysicalLink(SOURCE_STARTER_OP, PortIdentity(), physicalOp.id, PortIdentity()) ) // special case for source operator } else { - physicalOp.getInputLinks().toSet + val additionalLinks = mutable.HashSet[PhysicalLink]() + if(this.operator.isInstanceOf[LoopStartOpExec]){ + additionalLinks.add(ResumeLoopHandler.loopToSelfLink) + } + (additionalLinks ++ physicalOp.getInputLinks()).toSet } ) this.outputIterator.setTupleOutput(currentOutputIterator) @@ -206,13 +189,22 @@ class DataProcessor( case _ => outputTupleCount } + var loopI = 0 + operator match { + case exec: LoopStartOpExec => + loopI = exec.iteration + case exec: LoopEndOpExec => + loopI = exec.iteration + case _ => //do nothing + } WorkerStatistics( stateManager.getCurrentState, inputTupleCount, displayOut, dataProcessingTime, controlProcessingTime, - totalExecutionTime - dataProcessingTime - controlProcessingTime + totalExecutionTime - dataProcessingTime - controlProcessingTime, + loopI ) } @@ -231,7 +223,8 @@ class DataProcessor( asyncRPCClient ) ) - if (tuple.isLeft) { + // logger.info(s"$tuple, $inputTupleCount, $outputTupleCount") + if (tuple.isLeft && !tuple.left.get.isInstanceOf[EndOfIteration]) { inputTupleCount += 1 } } catch safely { @@ -347,10 +340,10 @@ class DataProcessor( initBatch(channelId, tuples) processInputTuple(Left(inputBatch(currentInputIdx))) case EndOfUpstream() => - if (operator.isInstanceOf[LoopStartOpExec]) { - processInputTuple(Right(InputExhausted())) - return - } +// if (operator.isInstanceOf[LoopStartOpExec]) { +// processInputTuple(Right(InputExhausted())) +// return +// } val currentLink = upstreamLinkStatus.getInputLink(channelId.fromWorkerId) upstreamLinkStatus.markWorkerEOF(channelId.fromWorkerId) if (upstreamLinkStatus.isLinkEOF(currentLink)) { diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/OpenOperatorHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/OpenOperatorHandler.scala index cee60616000..693f908f5e8 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/OpenOperatorHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/OpenOperatorHandler.scala @@ -2,7 +2,12 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.OpenOperatorHandler.OpenOperator +import edu.uci.ics.amber.engine.common.{ISinkOperatorExecutor, ISourceOperatorExecutor} import edu.uci.ics.amber.engine.common.rpc.AsyncRPCServer.ControlCommand +import edu.uci.ics.amber.engine.common.virtualidentity.util.{SOURCE_STARTER_ACTOR, SOURCE_STARTER_OP} +import edu.uci.ics.amber.engine.common.workflow.{PhysicalLink, PortIdentity} +import edu.uci.ics.texera.workflow.common.operators.OperatorExecutor +import edu.uci.ics.texera.workflow.operators.loop.LoopStartOpExec object OpenOperatorHandler { @@ -13,5 +18,12 @@ trait OpenOperatorHandler { this: DataProcessorRPCHandlerInitializer => registerHandler { (openOperator: OpenOperator, sender) => dp.operator.open() + dp.operator match { + case executor: LoopStartOpExec => dp.registerInput( + ResumeLoopHandler.loopSelf, + ResumeLoopHandler.loopToSelfLink + ) + case _ => // skip + } } } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala index 30de9096133..4b5cce36386 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala @@ -1,24 +1,35 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers -import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.ResumeLoopHandler.ResumeLoop +import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.EndOfIteration +import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.ResumeLoopHandler.{ResumeLoop, loopToSelfChannelId} import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer import edu.uci.ics.amber.engine.common.ambermessage.{DataFrame, EndOfUpstream} import edu.uci.ics.amber.engine.common.rpc.AsyncRPCServer.ControlCommand +import edu.uci.ics.amber.engine.common.virtualidentity.{ActorVirtualIdentity, ChannelIdentity, OperatorIdentity, PhysicalOpIdentity} +import edu.uci.ics.amber.engine.common.workflow.{PhysicalLink, PortIdentity} import edu.uci.ics.texera.workflow.operators.loop.LoopStartOpExec object ResumeLoopHandler { final case class ResumeLoop() extends ControlCommand[Unit] -} + + val loopToSelfLink = PhysicalLink(ResumeLoopHandler.loopSelfOp, PortIdentity(), ResumeLoopHandler.loopSelfOp, PortIdentity()) + val loopSelfOp = PhysicalOpIdentity(OperatorIdentity("loopSelf"), "loopSelf") + val loopSelf = ActorVirtualIdentity("loopSelf") + val loopToSelfChannelId = ChannelIdentity(loopSelf, loopSelf, isControl = false)} trait ResumeLoopHandler { this: DataProcessorRPCHandlerInitializer => registerHandler { (_: ResumeLoop, _) => { + val ls = dp.operator.asInstanceOf[LoopStartOpExec] + if (ls.iteration < ls.i){ dp.processDataPayload( - dp.currentBatchChannel, - DataFrame(dp.operator.asInstanceOf[LoopStartOpExec].buffer.toArray) + loopToSelfChannelId, + DataFrame(ls.buffer.toArray ++ Array(EndOfIteration(dp.actorId))) ) - dp.processDataPayload(dp.currentBatchChannel, EndOfUpstream()) + }else{ + dp.processDataPayload(loopToSelfChannelId, EndOfUpstream()) + } } } } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/websocket/event/OperatorStatisticsUpdateEvent.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/websocket/event/OperatorStatisticsUpdateEvent.scala index 961856e6b60..905c73ededa 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/websocket/event/OperatorStatisticsUpdateEvent.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/websocket/event/OperatorStatisticsUpdateEvent.scala @@ -7,7 +7,8 @@ case class OperatorStatistics( numWorkers: Long, aggregatedDataProcessingTime: Long, aggregatedControlProcessingTime: Long, - aggregatedIdleTime: Long + aggregatedIdleTime: Long, + loopI:Long ) case class OperatorStatisticsUpdateEvent(operatorStatistics: Map[String, OperatorStatistics]) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ExecutionStatsService.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ExecutionStatsService.scala index 7deed31284d..fa2351fa60d 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ExecutionStatsService.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ExecutionStatsService.scala @@ -85,7 +85,8 @@ class ExecutionStatsService( stats.numWorkers, stats.dataProcessingTime, stats.controlProcessingTime, - stats.idleTime + stats.idleTime, + stats.loopI ) (x._1, res) }) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala index 40bd03f2846..0f1369b3626 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala @@ -27,7 +27,7 @@ class LoopStartOpDesc extends LogicalOp { executionId, operatorIdentifier, OpExecInitInfo((_, _, operatorConfig) => { - new LoopStartOpExec(operatorConfig.workerConfigs.head.workerId) + new LoopStartOpExec(operatorConfig.workerConfigs.head.workerId, i) }) ) .withInputPorts(operatorInfo.inputPorts, inputPortToSchemaMapping) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala index 486360071a8..ea4b1301b8b 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala @@ -12,10 +12,9 @@ import edu.uci.ics.texera.workflow.common.tuple.Tuple import scala.collection.mutable -class LoopStartOpExec(val workerId: ActorVirtualIdentity) extends OperatorExecutor { - var count = 0 +class LoopStartOpExec(val workerId: ActorVirtualIdentity, val i: Int) extends OperatorExecutor { + var iteration = 0 var buffer = new mutable.ArrayBuffer[ITuple] - override def processTuple( tuple: Either[ITuple, InputExhausted], input: Int, @@ -23,10 +22,17 @@ class LoopStartOpExec(val workerId: ActorVirtualIdentity) extends OperatorExecut asyncRPCClient: AsyncRPCClient ): Iterator[(ITuple, Option[PortIdentity])] = { tuple match { - case Left(t) => + case Left(t: EndOfIteration) => + iteration += 1 + Iterator((t, None)) + case Left(t) if iteration == 0 => buffer.append(t) Iterator((t, None)) - case Right(_) => Iterator((EndOfIteration(workerId), None)) + case Right(_) if iteration == 0 => + iteration += 1 + Iterator((EndOfIteration(workerId), None)) + case Right(_) => + Iterator.empty } } diff --git a/core/amber/src/main/scalapb/edu/uci/ics/amber/engine/architecture/worker/statistics/StatisticsProto.scala b/core/amber/src/main/scalapb/edu/uci/ics/amber/engine/architecture/worker/statistics/StatisticsProto.scala index b1d4a38d539..188a8e276e9 100644 --- a/core/amber/src/main/scalapb/edu/uci/ics/amber/engine/architecture/worker/statistics/StatisticsProto.scala +++ b/core/amber/src/main/scalapb/edu/uci/ics/amber/engine/architecture/worker/statistics/StatisticsProto.scala @@ -16,15 +16,15 @@ object StatisticsProto extends _root_.scalapb.GeneratedFileObject { private lazy val ProtoBytes: _root_.scala.Array[Byte] = scalapb.Encoding.fromBase64(scala.collection.immutable.Seq( """Cj1lZHUvdWNpL2ljcy9hbWJlci9lbmdpbmUvYXJjaGl0ZWN0dXJlL3dvcmtlci9zdGF0aXN0aWNzLnByb3RvEixlZHUudWNpL - mljcy5hbWJlci5lbmdpbmUuYXJjaGl0ZWN0dXJlLndvcmtlchoVc2NhbGFwYi9zY2FsYXBiLnByb3RvItcDChBXb3JrZXJTdGF0a + mljcy5hbWJlci5lbmdpbmUuYXJjaGl0ZWN0dXJlLndvcmtlchoVc2NhbGFwYi9zY2FsYXBiLnByb3RvIvoDChBXb3JrZXJTdGF0a XN0aWNzEnEKDHdvcmtlcl9zdGF0ZRgBIAEoDjI5LmVkdS51Y2kuaWNzLmFtYmVyLmVuZ2luZS5hcmNoaXRlY3R1cmUud29ya2VyL ldvcmtlclN0YXRlQhPiPxASC3dvcmtlclN0YXRl8AEBUgt3b3JrZXJTdGF0ZRJAChFpbnB1dF90dXBsZV9jb3VudBgCIAEoA0IU4 j8REg9pbnB1dFR1cGxlQ291bnRSD2lucHV0VHVwbGVDb3VudBJDChJvdXRwdXRfdHVwbGVfY291bnQYAyABKANCFeI/EhIQb3V0c HV0VHVwbGVDb3VudFIQb3V0cHV0VHVwbGVDb3VudBJJChRkYXRhX3Byb2Nlc3NpbmdfdGltZRgEIAEoA0IX4j8UEhJkYXRhUHJvY 2Vzc2luZ1RpbWVSEmRhdGFQcm9jZXNzaW5nVGltZRJSChdjb250cm9sX3Byb2Nlc3NpbmdfdGltZRgFIAEoA0Ia4j8XEhVjb250c m9sUHJvY2Vzc2luZ1RpbWVSFWNvbnRyb2xQcm9jZXNzaW5nVGltZRIqCglpZGxlX3RpbWUYBiABKANCDeI/ChIIaWRsZVRpbWVSC - GlkbGVUaW1lKlMKC1dvcmtlclN0YXRlEhEKDVVOSU5JVElBTElaRUQQABIJCgVSRUFEWRABEgsKB1JVTk5JTkcQAhIKCgZQQVVTR - UQQAxINCglDT01QTEVURUQQBEIJ4j8GSABYAHgBYgZwcm90bzM=""" + GlkbGVUaW1lEiEKBmxvb3BfaRgHIAEoA0IK4j8HEgVsb29wSVIFbG9vcEkqUwoLV29ya2VyU3RhdGUSEQoNVU5JTklUSUFMSVpFR + BAAEgkKBVJFQURZEAESCwoHUlVOTklORxACEgoKBlBBVVNFRBADEg0KCUNPTVBMRVRFRBAEQgniPwZIAFgAeAFiBnByb3RvMw==""" ).mkString) lazy val scalaDescriptor: _root_.scalapb.descriptors.FileDescriptor = { val scalaProto = com.google.protobuf.descriptor.FileDescriptorProto.parseFrom(ProtoBytes) diff --git a/core/amber/src/main/scalapb/edu/uci/ics/amber/engine/architecture/worker/statistics/WorkerStatistics.scala b/core/amber/src/main/scalapb/edu/uci/ics/amber/engine/architecture/worker/statistics/WorkerStatistics.scala index 29eb0bd2ef2..52894d9084d 100644 --- a/core/amber/src/main/scalapb/edu/uci/ics/amber/engine/architecture/worker/statistics/WorkerStatistics.scala +++ b/core/amber/src/main/scalapb/edu/uci/ics/amber/engine/architecture/worker/statistics/WorkerStatistics.scala @@ -12,7 +12,8 @@ final case class WorkerStatistics( outputTupleCount: _root_.scala.Long, dataProcessingTime: _root_.scala.Long, controlProcessingTime: _root_.scala.Long, - idleTime: _root_.scala.Long + idleTime: _root_.scala.Long, + loopI: _root_.scala.Long ) extends scalapb.GeneratedMessage with scalapb.lenses.Updatable[WorkerStatistics] { @transient private[this] var __serializedSizeCachedValue: _root_.scala.Int = 0 @@ -60,6 +61,13 @@ final case class WorkerStatistics( __size += _root_.com.google.protobuf.CodedOutputStream.computeInt64Size(6, __value) } }; + + { + val __value = loopI + if (__value != 0L) { + __size += _root_.com.google.protobuf.CodedOutputStream.computeInt64Size(7, __value) + } + }; __size } override def serializedSize: _root_.scala.Int = { @@ -107,6 +115,12 @@ final case class WorkerStatistics( _output__.writeInt64(6, __v) } }; + { + val __v = loopI + if (__v != 0L) { + _output__.writeInt64(7, __v) + } + }; } def withWorkerState(__v: edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState): WorkerStatistics = copy(workerState = __v) def withInputTupleCount(__v: _root_.scala.Long): WorkerStatistics = copy(inputTupleCount = __v) @@ -114,6 +128,7 @@ final case class WorkerStatistics( def withDataProcessingTime(__v: _root_.scala.Long): WorkerStatistics = copy(dataProcessingTime = __v) def withControlProcessingTime(__v: _root_.scala.Long): WorkerStatistics = copy(controlProcessingTime = __v) def withIdleTime(__v: _root_.scala.Long): WorkerStatistics = copy(idleTime = __v) + def withLoopI(__v: _root_.scala.Long): WorkerStatistics = copy(loopI = __v) def getFieldByNumber(__fieldNumber: _root_.scala.Int): _root_.scala.Any = { (__fieldNumber: @_root_.scala.unchecked) match { case 1 => { @@ -140,6 +155,10 @@ final case class WorkerStatistics( val __t = idleTime if (__t != 0L) __t else null } + case 7 => { + val __t = loopI + if (__t != 0L) __t else null + } } } def getField(__field: _root_.scalapb.descriptors.FieldDescriptor): _root_.scalapb.descriptors.PValue = { @@ -151,6 +170,7 @@ final case class WorkerStatistics( case 4 => _root_.scalapb.descriptors.PLong(dataProcessingTime) case 5 => _root_.scalapb.descriptors.PLong(controlProcessingTime) case 6 => _root_.scalapb.descriptors.PLong(idleTime) + case 7 => _root_.scalapb.descriptors.PLong(loopI) } } def toProtoString: _root_.scala.Predef.String = _root_.scalapb.TextFormat.printToSingleLineUnicodeString(this) @@ -167,6 +187,7 @@ object WorkerStatistics extends scalapb.GeneratedMessageCompanion[edu.uci.ics.am var __dataProcessingTime: _root_.scala.Long = 0L var __controlProcessingTime: _root_.scala.Long = 0L var __idleTime: _root_.scala.Long = 0L + var __loopI: _root_.scala.Long = 0L var _done__ = false while (!_done__) { val _tag__ = _input__.readTag() @@ -184,6 +205,8 @@ object WorkerStatistics extends scalapb.GeneratedMessageCompanion[edu.uci.ics.am __controlProcessingTime = _input__.readInt64() case 48 => __idleTime = _input__.readInt64() + case 56 => + __loopI = _input__.readInt64() case tag => _input__.skipField(tag) } } @@ -193,7 +216,8 @@ object WorkerStatistics extends scalapb.GeneratedMessageCompanion[edu.uci.ics.am outputTupleCount = __outputTupleCount, dataProcessingTime = __dataProcessingTime, controlProcessingTime = __controlProcessingTime, - idleTime = __idleTime + idleTime = __idleTime, + loopI = __loopI ) } implicit def messageReads: _root_.scalapb.descriptors.Reads[edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerStatistics] = _root_.scalapb.descriptors.Reads{ @@ -205,7 +229,8 @@ object WorkerStatistics extends scalapb.GeneratedMessageCompanion[edu.uci.ics.am outputTupleCount = __fieldsMap.get(scalaDescriptor.findFieldByNumber(3).get).map(_.as[_root_.scala.Long]).getOrElse(0L), dataProcessingTime = __fieldsMap.get(scalaDescriptor.findFieldByNumber(4).get).map(_.as[_root_.scala.Long]).getOrElse(0L), controlProcessingTime = __fieldsMap.get(scalaDescriptor.findFieldByNumber(5).get).map(_.as[_root_.scala.Long]).getOrElse(0L), - idleTime = __fieldsMap.get(scalaDescriptor.findFieldByNumber(6).get).map(_.as[_root_.scala.Long]).getOrElse(0L) + idleTime = __fieldsMap.get(scalaDescriptor.findFieldByNumber(6).get).map(_.as[_root_.scala.Long]).getOrElse(0L), + loopI = __fieldsMap.get(scalaDescriptor.findFieldByNumber(7).get).map(_.as[_root_.scala.Long]).getOrElse(0L) ) case _ => throw new RuntimeException("Expected PMessage") } @@ -224,7 +249,8 @@ object WorkerStatistics extends scalapb.GeneratedMessageCompanion[edu.uci.ics.am outputTupleCount = 0L, dataProcessingTime = 0L, controlProcessingTime = 0L, - idleTime = 0L + idleTime = 0L, + loopI = 0L ) implicit class WorkerStatisticsLens[UpperPB](_l: _root_.scalapb.lenses.Lens[UpperPB, edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerStatistics]) extends _root_.scalapb.lenses.ObjectLens[UpperPB, edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerStatistics](_l) { def workerState: _root_.scalapb.lenses.Lens[UpperPB, edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState] = field(_.workerState)((c_, f_) => c_.copy(workerState = f_)) @@ -233,6 +259,7 @@ object WorkerStatistics extends scalapb.GeneratedMessageCompanion[edu.uci.ics.am def dataProcessingTime: _root_.scalapb.lenses.Lens[UpperPB, _root_.scala.Long] = field(_.dataProcessingTime)((c_, f_) => c_.copy(dataProcessingTime = f_)) def controlProcessingTime: _root_.scalapb.lenses.Lens[UpperPB, _root_.scala.Long] = field(_.controlProcessingTime)((c_, f_) => c_.copy(controlProcessingTime = f_)) def idleTime: _root_.scalapb.lenses.Lens[UpperPB, _root_.scala.Long] = field(_.idleTime)((c_, f_) => c_.copy(idleTime = f_)) + def loopI: _root_.scalapb.lenses.Lens[UpperPB, _root_.scala.Long] = field(_.loopI)((c_, f_) => c_.copy(loopI = f_)) } final val WORKER_STATE_FIELD_NUMBER = 1 final val INPUT_TUPLE_COUNT_FIELD_NUMBER = 2 @@ -240,20 +267,23 @@ object WorkerStatistics extends scalapb.GeneratedMessageCompanion[edu.uci.ics.am final val DATA_PROCESSING_TIME_FIELD_NUMBER = 4 final val CONTROL_PROCESSING_TIME_FIELD_NUMBER = 5 final val IDLE_TIME_FIELD_NUMBER = 6 + final val LOOP_I_FIELD_NUMBER = 7 def of( workerState: edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState, inputTupleCount: _root_.scala.Long, outputTupleCount: _root_.scala.Long, dataProcessingTime: _root_.scala.Long, controlProcessingTime: _root_.scala.Long, - idleTime: _root_.scala.Long + idleTime: _root_.scala.Long, + loopI: _root_.scala.Long ): _root_.edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerStatistics = _root_.edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerStatistics( workerState, inputTupleCount, outputTupleCount, dataProcessingTime, controlProcessingTime, - idleTime + idleTime, + loopI ) // @@protoc_insertion_point(GeneratedMessageCompanion[edu.uci.ics.amber.engine.architecture.worker.WorkerStatistics]) } diff --git a/core/amber/src/main/scalapb/edu/uci/ics/texera/web/workflowruntimestate/OperatorRuntimeStats.scala b/core/amber/src/main/scalapb/edu/uci/ics/texera/web/workflowruntimestate/OperatorRuntimeStats.scala index bae162d13d5..ac96e49246e 100644 --- a/core/amber/src/main/scalapb/edu/uci/ics/texera/web/workflowruntimestate/OperatorRuntimeStats.scala +++ b/core/amber/src/main/scalapb/edu/uci/ics/texera/web/workflowruntimestate/OperatorRuntimeStats.scala @@ -13,7 +13,8 @@ final case class OperatorRuntimeStats( numWorkers: _root_.scala.Int = 0, dataProcessingTime: _root_.scala.Long = 0L, controlProcessingTime: _root_.scala.Long = 0L, - idleTime: _root_.scala.Long = 0L + idleTime: _root_.scala.Long = 0L, + loopI: _root_.scala.Long = 0L ) extends scalapb.GeneratedMessage with scalapb.lenses.Updatable[OperatorRuntimeStats] { @transient private[this] var __serializedSizeCachedValue: _root_.scala.Int = 0 @@ -68,6 +69,13 @@ final case class OperatorRuntimeStats( __size += _root_.com.google.protobuf.CodedOutputStream.computeInt64Size(7, __value) } }; + + { + val __value = loopI + if (__value != 0L) { + __size += _root_.com.google.protobuf.CodedOutputStream.computeInt64Size(8, __value) + } + }; __size } override def serializedSize: _root_.scala.Int = { @@ -121,6 +129,12 @@ final case class OperatorRuntimeStats( _output__.writeInt64(7, __v) } }; + { + val __v = loopI + if (__v != 0L) { + _output__.writeInt64(8, __v) + } + }; } def withState(__v: edu.uci.ics.texera.web.workflowruntimestate.WorkflowAggregatedState): OperatorRuntimeStats = copy(state = __v) def withInputCount(__v: _root_.scala.Long): OperatorRuntimeStats = copy(inputCount = __v) @@ -129,6 +143,7 @@ final case class OperatorRuntimeStats( def withDataProcessingTime(__v: _root_.scala.Long): OperatorRuntimeStats = copy(dataProcessingTime = __v) def withControlProcessingTime(__v: _root_.scala.Long): OperatorRuntimeStats = copy(controlProcessingTime = __v) def withIdleTime(__v: _root_.scala.Long): OperatorRuntimeStats = copy(idleTime = __v) + def withLoopI(__v: _root_.scala.Long): OperatorRuntimeStats = copy(loopI = __v) def getFieldByNumber(__fieldNumber: _root_.scala.Int): _root_.scala.Any = { (__fieldNumber: @_root_.scala.unchecked) match { case 1 => { @@ -159,6 +174,10 @@ final case class OperatorRuntimeStats( val __t = idleTime if (__t != 0L) __t else null } + case 8 => { + val __t = loopI + if (__t != 0L) __t else null + } } } def getField(__field: _root_.scalapb.descriptors.FieldDescriptor): _root_.scalapb.descriptors.PValue = { @@ -171,6 +190,7 @@ final case class OperatorRuntimeStats( case 5 => _root_.scalapb.descriptors.PLong(dataProcessingTime) case 6 => _root_.scalapb.descriptors.PLong(controlProcessingTime) case 7 => _root_.scalapb.descriptors.PLong(idleTime) + case 8 => _root_.scalapb.descriptors.PLong(loopI) } } def toProtoString: _root_.scala.Predef.String = _root_.scalapb.TextFormat.printToSingleLineUnicodeString(this) @@ -188,6 +208,7 @@ object OperatorRuntimeStats extends scalapb.GeneratedMessageCompanion[edu.uci.ic var __dataProcessingTime: _root_.scala.Long = 0L var __controlProcessingTime: _root_.scala.Long = 0L var __idleTime: _root_.scala.Long = 0L + var __loopI: _root_.scala.Long = 0L var _done__ = false while (!_done__) { val _tag__ = _input__.readTag() @@ -207,6 +228,8 @@ object OperatorRuntimeStats extends scalapb.GeneratedMessageCompanion[edu.uci.ic __controlProcessingTime = _input__.readInt64() case 56 => __idleTime = _input__.readInt64() + case 64 => + __loopI = _input__.readInt64() case tag => _input__.skipField(tag) } } @@ -217,7 +240,8 @@ object OperatorRuntimeStats extends scalapb.GeneratedMessageCompanion[edu.uci.ic numWorkers = __numWorkers, dataProcessingTime = __dataProcessingTime, controlProcessingTime = __controlProcessingTime, - idleTime = __idleTime + idleTime = __idleTime, + loopI = __loopI ) } implicit def messageReads: _root_.scalapb.descriptors.Reads[edu.uci.ics.texera.web.workflowruntimestate.OperatorRuntimeStats] = _root_.scalapb.descriptors.Reads{ @@ -230,7 +254,8 @@ object OperatorRuntimeStats extends scalapb.GeneratedMessageCompanion[edu.uci.ic numWorkers = __fieldsMap.get(scalaDescriptor.findFieldByNumber(4).get).map(_.as[_root_.scala.Int]).getOrElse(0), dataProcessingTime = __fieldsMap.get(scalaDescriptor.findFieldByNumber(5).get).map(_.as[_root_.scala.Long]).getOrElse(0L), controlProcessingTime = __fieldsMap.get(scalaDescriptor.findFieldByNumber(6).get).map(_.as[_root_.scala.Long]).getOrElse(0L), - idleTime = __fieldsMap.get(scalaDescriptor.findFieldByNumber(7).get).map(_.as[_root_.scala.Long]).getOrElse(0L) + idleTime = __fieldsMap.get(scalaDescriptor.findFieldByNumber(7).get).map(_.as[_root_.scala.Long]).getOrElse(0L), + loopI = __fieldsMap.get(scalaDescriptor.findFieldByNumber(8).get).map(_.as[_root_.scala.Long]).getOrElse(0L) ) case _ => throw new RuntimeException("Expected PMessage") } @@ -250,7 +275,8 @@ object OperatorRuntimeStats extends scalapb.GeneratedMessageCompanion[edu.uci.ic numWorkers = 0, dataProcessingTime = 0L, controlProcessingTime = 0L, - idleTime = 0L + idleTime = 0L, + loopI = 0L ) implicit class OperatorRuntimeStatsLens[UpperPB](_l: _root_.scalapb.lenses.Lens[UpperPB, edu.uci.ics.texera.web.workflowruntimestate.OperatorRuntimeStats]) extends _root_.scalapb.lenses.ObjectLens[UpperPB, edu.uci.ics.texera.web.workflowruntimestate.OperatorRuntimeStats](_l) { def state: _root_.scalapb.lenses.Lens[UpperPB, edu.uci.ics.texera.web.workflowruntimestate.WorkflowAggregatedState] = field(_.state)((c_, f_) => c_.copy(state = f_)) @@ -260,6 +286,7 @@ object OperatorRuntimeStats extends scalapb.GeneratedMessageCompanion[edu.uci.ic def dataProcessingTime: _root_.scalapb.lenses.Lens[UpperPB, _root_.scala.Long] = field(_.dataProcessingTime)((c_, f_) => c_.copy(dataProcessingTime = f_)) def controlProcessingTime: _root_.scalapb.lenses.Lens[UpperPB, _root_.scala.Long] = field(_.controlProcessingTime)((c_, f_) => c_.copy(controlProcessingTime = f_)) def idleTime: _root_.scalapb.lenses.Lens[UpperPB, _root_.scala.Long] = field(_.idleTime)((c_, f_) => c_.copy(idleTime = f_)) + def loopI: _root_.scalapb.lenses.Lens[UpperPB, _root_.scala.Long] = field(_.loopI)((c_, f_) => c_.copy(loopI = f_)) } final val STATE_FIELD_NUMBER = 1 final val INPUT_COUNT_FIELD_NUMBER = 2 @@ -268,6 +295,7 @@ object OperatorRuntimeStats extends scalapb.GeneratedMessageCompanion[edu.uci.ic final val DATA_PROCESSING_TIME_FIELD_NUMBER = 5 final val CONTROL_PROCESSING_TIME_FIELD_NUMBER = 6 final val IDLE_TIME_FIELD_NUMBER = 7 + final val LOOP_I_FIELD_NUMBER = 8 def of( state: edu.uci.ics.texera.web.workflowruntimestate.WorkflowAggregatedState, inputCount: _root_.scala.Long, @@ -275,7 +303,8 @@ object OperatorRuntimeStats extends scalapb.GeneratedMessageCompanion[edu.uci.ic numWorkers: _root_.scala.Int, dataProcessingTime: _root_.scala.Long, controlProcessingTime: _root_.scala.Long, - idleTime: _root_.scala.Long + idleTime: _root_.scala.Long, + loopI: _root_.scala.Long ): _root_.edu.uci.ics.texera.web.workflowruntimestate.OperatorRuntimeStats = _root_.edu.uci.ics.texera.web.workflowruntimestate.OperatorRuntimeStats( state, inputCount, @@ -283,7 +312,8 @@ object OperatorRuntimeStats extends scalapb.GeneratedMessageCompanion[edu.uci.ic numWorkers, dataProcessingTime, controlProcessingTime, - idleTime + idleTime, + loopI ) // @@protoc_insertion_point(GeneratedMessageCompanion[edu.uci.ics.texera.web.OperatorRuntimeStats]) } diff --git a/core/amber/src/main/scalapb/edu/uci/ics/texera/web/workflowruntimestate/WorkflowruntimestateProto.scala b/core/amber/src/main/scalapb/edu/uci/ics/texera/web/workflowruntimestate/WorkflowruntimestateProto.scala index 3e4af2e8d4a..87c0ab4bce2 100644 --- a/core/amber/src/main/scalapb/edu/uci/ics/texera/web/workflowruntimestate/WorkflowruntimestateProto.scala +++ b/core/amber/src/main/scalapb/edu/uci/ics/texera/web/workflowruntimestate/WorkflowruntimestateProto.scala @@ -55,33 +55,34 @@ object WorkflowruntimestateProto extends _root_.scalapb.GeneratedFileObject { nRyeUIU4j8REg9vcGVyYXRvckNvbnNvbGVSD29wZXJhdG9yQ29uc29sZRqBAQoUT3BlcmF0b3JDb25zb2xlRW50cnkSGgoDa2V5G AEgASgJQgjiPwUSA2tleVIDa2V5EkkKBXZhbHVlGAIgASgLMicuZWR1LnVjaS5pY3MudGV4ZXJhLndlYi5PcGVyYXRvckNvbnNvb GVCCuI/BxIFdmFsdWVSBXZhbHVlOgI4ASJ2ChVPcGVyYXRvcldvcmtlck1hcHBpbmcSLwoKb3BlcmF0b3JJZBgBIAEoCUIP4j8ME - gpvcGVyYXRvcklkUgpvcGVyYXRvcklkEiwKCXdvcmtlcklkcxgCIAMoCUIO4j8LEgl3b3JrZXJJZHNSCXdvcmtlcklkcyLNAwoUT + gpvcGVyYXRvcklkUgpvcGVyYXRvcklkEiwKCXdvcmtlcklkcxgCIAMoCUIO4j8LEgl3b3JrZXJJZHNSCXdvcmtlcklkcyLwAwoUT 3BlcmF0b3JSdW50aW1lU3RhdHMSUQoFc3RhdGUYASABKA4yLy5lZHUudWNpLmljcy50ZXhlcmEud2ViLldvcmtmbG93QWdncmVnY XRlZFN0YXRlQgriPwcSBXN0YXRlUgVzdGF0ZRIwCgtpbnB1dF9jb3VudBgCIAEoA0IP4j8MEgppbnB1dENvdW50UgppbnB1dENvd W50EjMKDG91dHB1dF9jb3VudBgDIAEoA0IQ4j8NEgtvdXRwdXRDb3VudFILb3V0cHV0Q291bnQSMAoLbnVtX3dvcmtlcnMYBCABK AVCD+I/DBIKbnVtV29ya2Vyc1IKbnVtV29ya2VycxJJChRkYXRhX3Byb2Nlc3NpbmdfdGltZRgFIAEoA0IX4j8UEhJkYXRhUHJvY 2Vzc2luZ1RpbWVSEmRhdGFQcm9jZXNzaW5nVGltZRJSChdjb250cm9sX3Byb2Nlc3NpbmdfdGltZRgGIAEoA0Ia4j8XEhVjb250c m9sUHJvY2Vzc2luZ1RpbWVSFWNvbnRyb2xQcm9jZXNzaW5nVGltZRIqCglpZGxlX3RpbWUYByABKANCDeI/ChIIaWRsZVRpbWVSC - GlkbGVUaW1lIooEChNFeGVjdXRpb25TdGF0c1N0b3JlEjsKDnN0YXJ0VGltZVN0YW1wGAEgASgDQhPiPxASDnN0YXJ0VGltZVN0Y - W1wUg5zdGFydFRpbWVTdGFtcBI1CgxlbmRUaW1lU3RhbXAYAiABKANCEeI/DhIMZW5kVGltZVN0YW1wUgxlbmRUaW1lU3RhbXASd - QoNb3BlcmF0b3JfaW5mbxgDIAMoCzI9LmVkdS51Y2kuaWNzLnRleGVyYS53ZWIuRXhlY3V0aW9uU3RhdHNTdG9yZS5PcGVyYXRvc - kluZm9FbnRyeUIR4j8OEgxvcGVyYXRvckluZm9SDG9wZXJhdG9ySW5mbxKBAQoXb3BlcmF0b3Jfd29ya2VyX21hcHBpbmcYBCADK - AsyLS5lZHUudWNpLmljcy50ZXhlcmEud2ViLk9wZXJhdG9yV29ya2VyTWFwcGluZ0Ia4j8XEhVvcGVyYXRvcldvcmtlck1hcHBpb - mdSFW9wZXJhdG9yV29ya2VyTWFwcGluZxqDAQoRT3BlcmF0b3JJbmZvRW50cnkSGgoDa2V5GAEgASgJQgjiPwUSA2tleVIDa2V5E - k4KBXZhbHVlGAIgASgLMiwuZWR1LnVjaS5pY3MudGV4ZXJhLndlYi5PcGVyYXRvclJ1bnRpbWVTdGF0c0IK4j8HEgV2YWx1ZVIFd - mFsdWU6AjgBItQCChJXb3JrZmxvd0ZhdGFsRXJyb3ISRQoEdHlwZRgBIAEoDjImLmVkdS51Y2kuaWNzLnRleGVyYS53ZWIuRmF0Y - WxFcnJvclR5cGVCCeI/BhIEdHlwZVIEdHlwZRJLCgl0aW1lc3RhbXAYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQ - hHiPw4SCXRpbWVzdGFtcPABAVIJdGltZXN0YW1wEiYKB21lc3NhZ2UYAyABKAlCDOI/CRIHbWVzc2FnZVIHbWVzc2FnZRImCgdkZ - XRhaWxzGAQgASgJQgziPwkSB2RldGFpbHNSB2RldGFpbHMSLwoKb3BlcmF0b3JJZBgFIAEoCUIP4j8MEgpvcGVyYXRvcklkUgpvc - GVyYXRvcklkEikKCHdvcmtlcklkGAYgASgJQg3iPwoSCHdvcmtlcklkUgh3b3JrZXJJZCLvAgoWRXhlY3V0aW9uTWV0YWRhdGFTd - G9yZRJRCgVzdGF0ZRgBIAEoDjIvLmVkdS51Y2kuaWNzLnRleGVyYS53ZWIuV29ya2Zsb3dBZ2dyZWdhdGVkU3RhdGVCCuI/BxIFc - 3RhdGVSBXN0YXRlEl8KDGZhdGFsX2Vycm9ycxgCIAMoCzIqLmVkdS51Y2kuaWNzLnRleGVyYS53ZWIuV29ya2Zsb3dGYXRhbEVyc - m9yQhDiPw0SC2ZhdGFsRXJyb3JzUgtmYXRhbEVycm9ycxJpCgtleGVjdXRpb25JZBgDIAEoCzIyLmVkdS51Y2kuaWNzLmFtYmVyL - mVuZ2luZS5jb21tb24uRXhlY3V0aW9uSWRlbnRpdHlCE+I/EBILZXhlY3V0aW9uSWTwAQFSC2V4ZWN1dGlvbklkEjYKDWlzX3JlY - 292ZXJpbmcYBCABKAhCEeI/DhIMaXNSZWNvdmVyaW5nUgxpc1JlY292ZXJpbmcqPgoORmF0YWxFcnJvclR5cGUSFQoRQ09NUElMQ - VRJT05fRVJST1IQABIVChFFWEVDVVRJT05fRkFJTFVSRRABKp8BChdXb3JrZmxvd0FnZ3JlZ2F0ZWRTdGF0ZRIRCg1VTklOSVRJQ - UxJWkVEEAASCQoFUkVBRFkQARILCgdSVU5OSU5HEAISCwoHUEFVU0lORxADEgoKBlBBVVNFRBAEEgwKCFJFU1VNSU5HEAUSDQoJQ - 09NUExFVEVEEAYSCgoGRkFJTEVEEAcSCwoHVU5LTk9XThAIEgoKBktJTExFRBAJQgniPwZIAFgAeABiBnByb3RvMw==""" + GlkbGVUaW1lEiEKBmxvb3BfaRgIIAEoA0IK4j8HEgVsb29wSVIFbG9vcEkiigQKE0V4ZWN1dGlvblN0YXRzU3RvcmUSOwoOc3Rhc + nRUaW1lU3RhbXAYASABKANCE+I/EBIOc3RhcnRUaW1lU3RhbXBSDnN0YXJ0VGltZVN0YW1wEjUKDGVuZFRpbWVTdGFtcBgCIAEoA + 0IR4j8OEgxlbmRUaW1lU3RhbXBSDGVuZFRpbWVTdGFtcBJ1Cg1vcGVyYXRvcl9pbmZvGAMgAygLMj0uZWR1LnVjaS5pY3MudGV4Z + XJhLndlYi5FeGVjdXRpb25TdGF0c1N0b3JlLk9wZXJhdG9ySW5mb0VudHJ5QhHiPw4SDG9wZXJhdG9ySW5mb1IMb3BlcmF0b3JJb + mZvEoEBChdvcGVyYXRvcl93b3JrZXJfbWFwcGluZxgEIAMoCzItLmVkdS51Y2kuaWNzLnRleGVyYS53ZWIuT3BlcmF0b3JXb3JrZ + XJNYXBwaW5nQhriPxcSFW9wZXJhdG9yV29ya2VyTWFwcGluZ1IVb3BlcmF0b3JXb3JrZXJNYXBwaW5nGoMBChFPcGVyYXRvckluZ + m9FbnRyeRIaCgNrZXkYASABKAlCCOI/BRIDa2V5UgNrZXkSTgoFdmFsdWUYAiABKAsyLC5lZHUudWNpLmljcy50ZXhlcmEud2ViL + k9wZXJhdG9yUnVudGltZVN0YXRzQgriPwcSBXZhbHVlUgV2YWx1ZToCOAEi1AIKEldvcmtmbG93RmF0YWxFcnJvchJFCgR0eXBlG + AEgASgOMiYuZWR1LnVjaS5pY3MudGV4ZXJhLndlYi5GYXRhbEVycm9yVHlwZUIJ4j8GEgR0eXBlUgR0eXBlEksKCXRpbWVzdGFtc + BgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCEeI/DhIJdGltZXN0YW1w8AEBUgl0aW1lc3RhbXASJgoHbWVzc2FnZ + RgDIAEoCUIM4j8JEgdtZXNzYWdlUgdtZXNzYWdlEiYKB2RldGFpbHMYBCABKAlCDOI/CRIHZGV0YWlsc1IHZGV0YWlscxIvCgpvc + GVyYXRvcklkGAUgASgJQg/iPwwSCm9wZXJhdG9ySWRSCm9wZXJhdG9ySWQSKQoId29ya2VySWQYBiABKAlCDeI/ChIId29ya2VyS + WRSCHdvcmtlcklkIu8CChZFeGVjdXRpb25NZXRhZGF0YVN0b3JlElEKBXN0YXRlGAEgASgOMi8uZWR1LnVjaS5pY3MudGV4ZXJhL + ndlYi5Xb3JrZmxvd0FnZ3JlZ2F0ZWRTdGF0ZUIK4j8HEgVzdGF0ZVIFc3RhdGUSXwoMZmF0YWxfZXJyb3JzGAIgAygLMiouZWR1L + nVjaS5pY3MudGV4ZXJhLndlYi5Xb3JrZmxvd0ZhdGFsRXJyb3JCEOI/DRILZmF0YWxFcnJvcnNSC2ZhdGFsRXJyb3JzEmkKC2V4Z + WN1dGlvbklkGAMgASgLMjIuZWR1LnVjaS5pY3MuYW1iZXIuZW5naW5lLmNvbW1vbi5FeGVjdXRpb25JZGVudGl0eUIT4j8QEgtle + GVjdXRpb25JZPABAVILZXhlY3V0aW9uSWQSNgoNaXNfcmVjb3ZlcmluZxgEIAEoCEIR4j8OEgxpc1JlY292ZXJpbmdSDGlzUmVjb + 3ZlcmluZyo+Cg5GYXRhbEVycm9yVHlwZRIVChFDT01QSUxBVElPTl9FUlJPUhAAEhUKEUVYRUNVVElPTl9GQUlMVVJFEAEqnwEKF + 1dvcmtmbG93QWdncmVnYXRlZFN0YXRlEhEKDVVOSU5JVElBTElaRUQQABIJCgVSRUFEWRABEgsKB1JVTk5JTkcQAhILCgdQQVVTS + U5HEAMSCgoGUEFVU0VEEAQSDAoIUkVTVU1JTkcQBRINCglDT01QTEVURUQQBhIKCgZGQUlMRUQQBxILCgdVTktOT1dOEAgSCgoGS + 0lMTEVEEAlCCeI/BkgAWAB4AGIGcHJvdG8z""" ).mkString) lazy val scalaDescriptor: _root_.scalapb.descriptors.FileDescriptor = { val scalaProto = com.google.protobuf.descriptor.FileDescriptorProto.parseFrom(ProtoBytes) diff --git a/core/new-gui/src/assets/operator_images/LoopEnd.png b/core/new-gui/src/assets/operator_images/LoopEnd.png new file mode 100644 index 0000000000000000000000000000000000000000..ee0f9ab6faccd328c102f214a7547d3e34d8359b GIT binary patch literal 5865 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 literal 0 HcmV?d00001 diff --git a/core/new-gui/src/assets/operator_images/LoopStart.png b/core/new-gui/src/assets/operator_images/LoopStart.png new file mode 100644 index 0000000000000000000000000000000000000000..7e5be023cdf6b64dd1bc140e5d75980455afeefb GIT binary patch literal 2138 zcmV-g2&MOlP)Px-6iGxuRCr$Pok@1vFbsy}TQ#e+JKw6wt+cDNN^a$bl4voq27m;>LBHSYqAkVY z$0rb+#7kG<@A`TD-M{p=3agGPM=EfMd@k!_*RSip?%sX$Fa2BAilA^a1?mO0>wb6l z?j5k2v68i*x_G;KNw`$Lu656Tc9Eo zuwW)j8SQ}zShIknU1*!20#9EcQB z6cz^>1a(cxTka+6Y@h~k>Vdf?xiG9J0X0-|fOc49G29qr3Tieum%s|bdO}cVHSgWK zRd`?mYB7L@zzV^7YEZqNBe1F-Ru$AD0sZM+llKS!)Id<7mwzo{Qv-F_Gg`;Ajf=V5 zcEPBP*Z_3}D+OT1L9Nzsk<(YzwwE$XlA1;kSW!?b@}lb|h!7T_9?%@2san@xK`cu5 z4NPGHDnVMNrYfLLkh*xZogYJ3fJ!!d(!e#T>u=Ub0F_K1J7MHlAR1IaI-00i9oPgi z9*BT73e;we!|nF0U1^@ zs7;RU1&&!a`tnAT$9|9)ZqY&gy58B9DER@RKu1jgs7DGB{-0nA5~$U$I4!jz2t$w; zqkgJLlynDUghNo9^B97VVtR@~AU#e0tKZ~t7t{tJ75|b-a~jdPl25tgMD{_gXRF?k zXh|dcz09jVg*qRav^@^AC+J29unB4*p2DAq9F9D=pW8RejG4((AV;*f{{Mr`-7oK{ z+EsgPgPL!YKX^Y6rKgkdgTv61*9U7K6zNnkYM3epFlN!gQDWcT~H&}?8PnHV1_x_cm~zVnY^)I>;EKn0{3-V~!vN)Oxq z1Xa7+7l8B!s6zQ=uyYedn!q}B{|`_Nx_vRvhYQil;ar^?Yf$$#9B^gF6_Z@Wbki%x ztAm4^J*sB6k1kr5l+3wnhXBsW4WL>Slvzs@udINoSG{+mM^%k8;7yegDI0JR1JDygFuPXM(N0BWhGCaCVhT#aw=V=z?de(toRjZVSEj!2b;lCwiOAI&NI40$xA|c}btX{twu`E7^XstQe;!h`;?wV2 z%Jo4-_kRvhHM@QHbw`UcQ|SIrz^bA*?VPi-I)#csRl~5VdQ*UDgxEX`@Jqdlp9+)We4}*#tzgGR)hetLR`( zU|H*=iJ9@^;87n?j-wUVwYy&yI3H%)=vntJ?akv=w?Xq4_}$+EM50(-LrH< z#{4!zlKpGYwa)zOG_vem6=HDBqaRts+|g(kR99Km4F_4&$W*8>sEl|tQ-hhs9_Vun z>gTnz5P+#bA;|?nZ4OexEW{zGgI7fekWRZ0s4d!pa$Jc7Dqv0LRAQSWb7U`2X@e|? zo0Q0)PVs8qDJuir)QU*(sEv*6XNeh_us{W@2^ces5|+~3$lf=OsRtia46fp6Wg4dG za4SL!HmH+;m@# zDsgnbrDHOvO~+D)^f3}JKy6IZ+tl4B?Q;+TwJ~8C^f8h$L2X1+(!N)UiejKPA}mF- zxHJ)4oya1?$hF7Y9dSk14)CI8Pk&?3lD+Fpmu$lmB4(g-(a!au8T+XF? z&3-<|(l-l&nh{o0IL%^I3J3n{a?L|445|^V{+yco^kdRID20W+G-h2trJ@70V|d`a zt^4IY5lN{uHH`wO6RauwE@t@doj)}B__y3}7UfZ?b`FmA)(Wr6Db&ar0hK8vsx4#E z73EQ58h~eJ1sL0ssI2 literal 0 HcmV?d00001 From 247586fde1bf56f41dff3d6c9a8eca093b91e119 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Mon, 5 Feb 2024 00:47:49 -0800 Subject: [PATCH 03/29] update --- .../promisehandlers/ResumeLoopHandler.scala | 2 +- .../operators/loop/LoopEndOpDesc.scala | 8 +----- .../operators/loop/LoopEndOpExec.scala | 4 +-- .../operators/loop/LoopStartOpDesc.scala | 4 +-- .../operators/loop/LoopStartOpExec.scala | 26 ++++++++++++------- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala index 4b5cce36386..93c225d4b86 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala @@ -22,7 +22,7 @@ trait ResumeLoopHandler { registerHandler { (_: ResumeLoop, _) => { val ls = dp.operator.asInstanceOf[LoopStartOpExec] - if (ls.iteration < ls.i){ + if (ls.iteration < ls.termination){ dp.processDataPayload( loopToSelfChannelId, DataFrame(ls.buffer.toArray ++ Array(EndOfIteration(dp.actorId))) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala index e74feea3300..86a003458d9 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala @@ -11,12 +11,6 @@ import edu.uci.ics.texera.workflow.common.operators.LogicalOp import edu.uci.ics.texera.workflow.common.tuple.schema.Schema class LoopEndOpDesc extends LogicalOp { - - @JsonProperty(required = true) - @JsonSchemaTitle("Iteration") - @JsonPropertyDescription("the max number of iterations") - var i: Int = _ - override def getPhysicalOp( workflowId: WorkflowIdentity, executionId: ExecutionIdentity @@ -26,7 +20,7 @@ class LoopEndOpDesc extends LogicalOp { workflowId, executionId, operatorIdentifier, - OpExecInitInfo((_, _, _) => new LoopEndOpExec(i)) + OpExecInitInfo((_, _, _) => new LoopEndOpExec()) ) .withInputPorts(operatorInfo.inputPorts, inputPortToSchemaMapping) .withOutputPorts(operatorInfo.outputPorts, outputPortToSchemaMapping) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala index 4063ff0bfcd..18046e4dcbd 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala @@ -8,8 +8,8 @@ import edu.uci.ics.texera.workflow.common.tuple.Tuple import scala.collection.mutable -class LoopEndOpExec(val iteration: Int) extends OperatorExecutor { - var i = 0 +class LoopEndOpExec extends OperatorExecutor { + var iteration = 0 var buffer = new mutable.ArrayBuffer[Tuple] override def open(): Unit = {} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala index 0f1369b3626..37227b537ec 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala @@ -15,7 +15,7 @@ class LoopStartOpDesc extends LogicalOp { @JsonProperty(required = true) @JsonSchemaTitle("Iteration") @JsonPropertyDescription("the max number of iterations") - var i: Int = _ + var termination: Int = _ override def getPhysicalOp( workflowId: WorkflowIdentity, @@ -27,7 +27,7 @@ class LoopStartOpDesc extends LogicalOp { executionId, operatorIdentifier, OpExecInitInfo((_, _, operatorConfig) => { - new LoopStartOpExec(operatorConfig.workerConfigs.head.workerId, i) + new LoopStartOpExec(operatorConfig.workerConfigs.head.workerId, termination) }) ) .withInputPorts(operatorInfo.inputPorts, inputPortToSchemaMapping) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala index ea4b1301b8b..797e80f2c40 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala @@ -12,7 +12,7 @@ import edu.uci.ics.texera.workflow.common.tuple.Tuple import scala.collection.mutable -class LoopStartOpExec(val workerId: ActorVirtualIdentity, val i: Int) extends OperatorExecutor { +class LoopStartOpExec(val workerId: ActorVirtualIdentity, val termination: Int) extends OperatorExecutor { var iteration = 0 var buffer = new mutable.ArrayBuffer[ITuple] override def processTuple( @@ -22,17 +22,23 @@ class LoopStartOpExec(val workerId: ActorVirtualIdentity, val i: Int) extends Op asyncRPCClient: AsyncRPCClient ): Iterator[(ITuple, Option[PortIdentity])] = { tuple match { - case Left(t: EndOfIteration) => - iteration += 1 + case Left(t) => + t match { + case t: EndOfIteration => + iteration += 1; + case t => + if(iteration == 0){ + buffer.append(t) + } + } Iterator((t, None)) - case Left(t) if iteration == 0 => - buffer.append(t) - Iterator((t, None)) - case Right(_) if iteration == 0 => - iteration += 1 - Iterator((EndOfIteration(workerId), None)) case Right(_) => - Iterator.empty + if(iteration == 0) { + iteration += 1; + Iterator((EndOfIteration(workerId), None)) + }else{ + Iterator.empty + } } } From 54bef8c50ee699cc4841175c0476490017b0b904 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Mon, 5 Feb 2024 02:40:11 -0800 Subject: [PATCH 04/29] update --- .../operators/loop/LoopStartOpDesc.scala | 20 ++++++++++++++++--- .../operators/loop/LoopStartOpExec.scala | 16 +++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala index 37227b537ec..cd664c9ae9a 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala @@ -8,7 +8,7 @@ import edu.uci.ics.amber.engine.common.virtualidentity.{ExecutionIdentity, Workf import edu.uci.ics.amber.engine.common.workflow.{InputPort, OutputPort} import edu.uci.ics.texera.workflow.common.metadata.{OperatorGroupConstants, OperatorInfo} import edu.uci.ics.texera.workflow.common.operators.LogicalOp -import edu.uci.ics.texera.workflow.common.tuple.schema.Schema +import edu.uci.ics.texera.workflow.common.tuple.schema.{AttributeType, Schema} class LoopStartOpDesc extends LogicalOp { @@ -17,17 +17,22 @@ class LoopStartOpDesc extends LogicalOp { @JsonPropertyDescription("the max number of iterations") var termination: Int = _ + @JsonProperty(defaultValue = "false") + @JsonSchemaTitle("Append Iteration Number") + var append: Boolean = false + override def getPhysicalOp( workflowId: WorkflowIdentity, executionId: ExecutionIdentity ): PhysicalOp = { + val outputSchema = outputPortToSchemaMapping(operatorInfo.outputPorts.head.id) PhysicalOp .oneToOnePhysicalOp( workflowId, executionId, operatorIdentifier, OpExecInitInfo((_, _, operatorConfig) => { - new LoopStartOpExec(operatorConfig.workerConfigs.head.workerId, termination) + new LoopStartOpExec(outputSchema, operatorConfig.workerConfigs.head.workerId, termination) }) ) .withInputPorts(operatorInfo.inputPorts, inputPortToSchemaMapping) @@ -45,6 +50,15 @@ class LoopStartOpDesc extends LogicalOp { supportReconfiguration = true ) - override def getOutputSchema(schemas: Array[Schema]): Schema = schemas(0) + override def getOutputSchema(schemas: Array[Schema]): Schema = { + if (append) { + Schema.newBuilder() + .add("Iteration", AttributeType.INTEGER) + .add(schemas(0)) + .build() + } else { + schemas(0) + } + } } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala index 797e80f2c40..9bb986e4d97 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala @@ -9,10 +9,11 @@ import edu.uci.ics.amber.engine.common.virtualidentity.ActorVirtualIdentity import edu.uci.ics.amber.engine.common.workflow.PortIdentity import edu.uci.ics.texera.workflow.common.operators.OperatorExecutor import edu.uci.ics.texera.workflow.common.tuple.Tuple +import edu.uci.ics.texera.workflow.common.tuple.schema.{Attribute, AttributeType, Schema} import scala.collection.mutable -class LoopStartOpExec(val workerId: ActorVirtualIdentity, val termination: Int) extends OperatorExecutor { +class LoopStartOpExec(val outputSchema: Schema, val workerId: ActorVirtualIdentity, val termination: Int) extends OperatorExecutor { var iteration = 0 var buffer = new mutable.ArrayBuffer[ITuple] override def processTuple( @@ -25,16 +26,23 @@ class LoopStartOpExec(val workerId: ActorVirtualIdentity, val termination: Int) case Left(t) => t match { case t: EndOfIteration => - iteration += 1; + iteration += 1 + Iterator((t, None)) case t => if(iteration == 0){ buffer.append(t) } + if (outputSchema.containsAttribute("Iteration")) { + Iterator((Tuple.newBuilder(outputSchema).add(outputSchema.getAttribute("Iteration"), iteration).add(t.asInstanceOf[Tuple]).build, None)) + } + else { + Iterator((t, None)) + } } - Iterator((t, None)) + case Right(_) => if(iteration == 0) { - iteration += 1; + iteration += 1 Iterator((EndOfIteration(workerId), None)) }else{ Iterator.empty From c5612797c838b3a8ff2d2fadcb96daf44fa23368 Mon Sep 17 00:00:00 2001 From: linxinyuan Date: Mon, 12 Feb 2024 19:53:07 -0800 Subject: [PATCH 05/29] update --- .../ics/amber/engine/architecture/worker/DataProcessor.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index 0e19a081eb3..24f21173eb7 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -13,7 +13,7 @@ import edu.uci.ics.amber.engine.architecture.logreplay.ReplayLogManager import edu.uci.ics.amber.engine.architecture.messaginglayer.{OutputManager, WorkerTimerService} import edu.uci.ics.amber.engine.architecture.scheduling.config.OperatorConfig import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{ - EndOfIteration. + EndOfIteration, DPOutputIterator, FinalizeOperator, FinalizePort @@ -329,9 +329,6 @@ class DataProcessor( // processInputTuple(Right(InputExhausted())) // return // } - val currentLink = upstreamLinkStatus.getInputLink(channelId.fromWorkerId) - upstreamLinkStatus.markWorkerEOF(channelId.fromWorkerId) - if (upstreamLinkStatus.isLinkEOF(currentLink)) { val channel = this.inputGateway.getChannel(channelId) val portId = channel.getPortId From 7fb131b52eb8486407fc9a17569b09c98c640f82 Mon Sep 17 00:00:00 2001 From: linxinyuan Date: Mon, 12 Feb 2024 20:06:30 -0800 Subject: [PATCH 06/29] update --- .../architecture/worker/DataProcessor.scala | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index 24f21173eb7..5932166f121 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -8,7 +8,10 @@ import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.PortComp import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.WorkerExecutionCompletedHandler.WorkerExecutionCompleted import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.WorkerExecutionStartedHandler.WorkerStateUpdated import edu.uci.ics.amber.engine.architecture.deploysemantics.PhysicalOp -import edu.uci.ics.amber.engine.architecture.deploysemantics.layer.{OpExecInitInfoWithCode, OpExecInitInfoWithFunc} +import edu.uci.ics.amber.engine.architecture.deploysemantics.layer.{ + OpExecInitInfoWithCode, + OpExecInitInfoWithFunc +} import edu.uci.ics.amber.engine.architecture.logreplay.ReplayLogManager import edu.uci.ics.amber.engine.architecture.messaginglayer.{OutputManager, WorkerTimerService} import edu.uci.ics.amber.engine.architecture.scheduling.config.OperatorConfig @@ -20,15 +23,29 @@ import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{ } import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.PauseHandler.PauseWorker import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.ResumeLoopHandler -import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState.{COMPLETED, PAUSED, READY, RUNNING} +import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState.{ + COMPLETED, + PAUSED, + READY, + RUNNING +} import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerStatistics import edu.uci.ics.amber.engine.common.ambermessage._ import edu.uci.ics.amber.engine.common.statetransition.WorkerStateManager import edu.uci.ics.amber.engine.common.tuple.ITuple import edu.uci.ics.amber.engine.common.virtualidentity.util.{CONTROLLER, SELF, SOURCE_STARTER_OP} -import edu.uci.ics.amber.engine.common.virtualidentity.{ActorVirtualIdentity, ChannelIdentity, PhysicalOpIdentity} +import edu.uci.ics.amber.engine.common.virtualidentity.{ + ActorVirtualIdentity, + ChannelIdentity, + PhysicalOpIdentity +} import edu.uci.ics.amber.engine.common.workflow.{PhysicalLink, PortIdentity} -import edu.uci.ics.amber.engine.common.{IOperatorExecutor, ISinkOperatorExecutor, InputExhausted, VirtualIdentityUtils} +import edu.uci.ics.amber.engine.common.{ + IOperatorExecutor, + ISinkOperatorExecutor, + InputExhausted, + VirtualIdentityUtils +} import edu.uci.ics.amber.error.ErrorUtils.{mkConsoleMessage, safely} import edu.uci.ics.texera.workflow.operators.loop.{LoopEndOpExec, LoopStartOpExec} @@ -103,19 +120,6 @@ class DataProcessor( } this.operatorConfig = operatorConfig this.physicalOp = physicalOp - this.upstreamLinkStatus.setAllUpstreamLinks( - if (physicalOp.isSourceOperator) { - Set( - PhysicalLink(SOURCE_STARTER_OP, PortIdentity(), physicalOp.id, PortIdentity()) - ) // special case for source operator - } else { - val additionalLinks = mutable.HashSet[PhysicalLink]() - if(this.operator.isInstanceOf[LoopStartOpExec]){ - additionalLinks.add(ResumeLoopHandler.loopToSelfLink) - } - (additionalLinks ++ physicalOp.getInputLinks()).toSet - } - ) this.outputIterator.setTupleOutput(currentOutputIterator) } From b45bdf95cb9b6eea42e53b4228cbae1211b286e9 Mon Sep 17 00:00:00 2001 From: linxinyuan Date: Mon, 12 Feb 2024 20:31:54 -0800 Subject: [PATCH 07/29] update --- .../worker/promisehandlers/OpenOperatorHandler.scala | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/OpenOperatorHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/OpenOperatorHandler.scala index 693f908f5e8..edb62b43c41 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/OpenOperatorHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/OpenOperatorHandler.scala @@ -18,12 +18,5 @@ trait OpenOperatorHandler { this: DataProcessorRPCHandlerInitializer => registerHandler { (openOperator: OpenOperator, sender) => dp.operator.open() - dp.operator match { - case executor: LoopStartOpExec => dp.registerInput( - ResumeLoopHandler.loopSelf, - ResumeLoopHandler.loopToSelfLink - ) - case _ => // skip - } } } From e4c76ee5cfbf3490856c477e087269cbcbff8886 Mon Sep 17 00:00:00 2001 From: linxinyuan Date: Mon, 12 Feb 2024 23:15:37 -0800 Subject: [PATCH 08/29] update --- .../metadata/OperatorGroupConstants.scala | 4 +- .../operators/loop/GeneratorOpDesc.scala | 40 +++++++++++++++++++ .../operators/loop/GeneratorOpExec.scala | 32 +++++++++++++++ .../operators/loop/LoopEndOpDesc.scala | 2 +- .../operators/loop/LoopStartOpDesc.scala | 2 +- 5 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala create mode 100644 core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/metadata/OperatorGroupConstants.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/metadata/OperatorGroupConstants.scala index 5490ade9430..268664f33c8 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/metadata/OperatorGroupConstants.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/metadata/OperatorGroupConstants.scala @@ -8,6 +8,7 @@ object OperatorGroupConstants { final val UTILITY_GROUP = "Utilities" final val UDF_GROUP = "User-defined Functions" final val VISUALIZATION_GROUP = "Visualization" + final val CONTROL_GROUP = "Control" /** * The order of the groups to show up in the frontend operator panel. @@ -20,7 +21,8 @@ object OperatorGroupConstants { GroupInfo(JOIN_GROUP, 3), GroupInfo(UTILITY_GROUP, 4), GroupInfo(UDF_GROUP, 5), - GroupInfo(VISUALIZATION_GROUP, 6) + GroupInfo(VISUALIZATION_GROUP, 6), + GroupInfo(CONTROL_GROUP, 7) ) } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala new file mode 100644 index 00000000000..66cd68bea6f --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala @@ -0,0 +1,40 @@ +package edu.uci.ics.texera.workflow.operators.loop + +import edu.uci.ics.amber.engine.architecture.deploysemantics.PhysicalOp +import edu.uci.ics.amber.engine.architecture.deploysemantics.layer.OpExecInitInfo +import edu.uci.ics.amber.engine.common.virtualidentity.{ExecutionIdentity, WorkflowIdentity} +import edu.uci.ics.amber.engine.common.workflow.{InputPort, OutputPort} +import edu.uci.ics.texera.workflow.common.metadata.{OperatorGroupConstants, OperatorInfo} +import edu.uci.ics.texera.workflow.common.operators.LogicalOp +import edu.uci.ics.texera.workflow.common.tuple.schema.Schema + +class GeneratorOpDesc extends LogicalOp { + override def getPhysicalOp( + workflowId: WorkflowIdentity, + executionId: ExecutionIdentity + ): PhysicalOp = { + PhysicalOp + .oneToOnePhysicalOp( + workflowId, + executionId, + operatorIdentifier, + OpExecInitInfo((_, _, _) => new LoopEndOpExec()) + ) + .withInputPorts(operatorInfo.inputPorts, inputPortToSchemaMapping) + .withOutputPorts(operatorInfo.outputPorts, outputPortToSchemaMapping) + .withSuggestedWorkerNum(1) + } + + override def operatorInfo: OperatorInfo = + OperatorInfo( + "Generator", + "Generator", + OperatorGroupConstants.CONTROL_GROUP, + inputPorts = List(InputPort()), + outputPorts = List(OutputPort()), + supportReconfiguration = true + ) + + override def getOutputSchema(schemas: Array[Schema]): Schema = schemas(0) + +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala new file mode 100644 index 00000000000..3e1074f331d --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala @@ -0,0 +1,32 @@ +package edu.uci.ics.texera.workflow.operators.loop + +import edu.uci.ics.amber.engine.architecture.worker.PauseManager +import edu.uci.ics.amber.engine.common.InputExhausted +import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient +import edu.uci.ics.texera.workflow.common.operators.OperatorExecutor +import edu.uci.ics.texera.workflow.common.tuple.Tuple + +import scala.collection.mutable + +class GeneratorOpExec extends OperatorExecutor { + var iteration = 0 + var buffer = new mutable.ArrayBuffer[Tuple] + + override def open(): Unit = {} + + override def close(): Unit = {} + + override def processTexeraTuple( + tuple: Either[Tuple, InputExhausted], + input: Int, + pauseManager: PauseManager, + asyncRPCClient: AsyncRPCClient + ): Iterator[Tuple] = { + tuple match { + case Left(t) => + buffer.append(t) + Iterator() + case Right(_) => buffer.iterator + } + } +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala index 86a003458d9..ce6aa1c841b 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala @@ -31,7 +31,7 @@ class LoopEndOpDesc extends LogicalOp { OperatorInfo( "LoopEnd", "Limit the number of output rows", - OperatorGroupConstants.UTILITY_GROUP, + OperatorGroupConstants.CONTROL_GROUP, inputPorts = List(InputPort()), outputPorts = List(OutputPort()), supportReconfiguration = true diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala index cd664c9ae9a..34fe1fede4f 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala @@ -44,7 +44,7 @@ class LoopStartOpDesc extends LogicalOp { OperatorInfo( "LoopStart", "Limit the number of output rows", - OperatorGroupConstants.UTILITY_GROUP, + OperatorGroupConstants.CONTROL_GROUP, inputPorts = List(InputPort()), outputPorts = List(OutputPort()), supportReconfiguration = true From a40e6198b6a15c6fa4e96d11d7071f5a3e01a4c0 Mon Sep 17 00:00:00 2001 From: linxinyuan Date: Mon, 12 Feb 2024 23:31:26 -0800 Subject: [PATCH 09/29] update --- .../workflow/common/operators/LogicalOp.scala | 3 +- .../operators/loop/GeneratorOpDesc.scala | 31 ++++++++++++++----- .../operators/loop/GeneratorOpExec.scala | 18 +++++------ 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala index be9599861a0..bd22f9dc050 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala @@ -28,7 +28,7 @@ import edu.uci.ics.texera.workflow.operators.intervalJoin.IntervalJoinOpDesc import edu.uci.ics.texera.workflow.operators.keywordSearch.KeywordSearchOpDesc import edu.uci.ics.texera.workflow.operators.limit.LimitOpDesc import edu.uci.ics.texera.workflow.operators.linearregression.LinearRegressionOpDesc -import edu.uci.ics.texera.workflow.operators.loop.{LoopEndOpDesc, LoopStartOpDesc} +import edu.uci.ics.texera.workflow.operators.loop.{GeneratorOpDesc, LoopEndOpDesc, LoopStartOpDesc} import edu.uci.ics.texera.workflow.operators.projection.ProjectionOpDesc import edu.uci.ics.texera.workflow.operators.randomksampling.RandomKSamplingOpDesc import edu.uci.ics.texera.workflow.operators.regex.RegexOpDesc @@ -140,6 +140,7 @@ trait StateTransferFunc new Type(value = classOf[TypeCastingOpDesc], name = "TypeCasting"), new Type(value = classOf[LimitOpDesc], name = "Limit"), new Type(value = classOf[LoopStartOpDesc], name = "LoopStart"), + new Type(value = classOf[GeneratorOpDesc], name = "Generator"), new Type(value = classOf[LoopEndOpDesc], name = "LoopEnd"), new Type(value = classOf[RandomKSamplingOpDesc], name = "RandomKSampling"), new Type(value = classOf[ReservoirSamplingOpDesc], name = "ReservoirSampling"), diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala index 66cd68bea6f..13a4237a256 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala @@ -6,14 +6,21 @@ import edu.uci.ics.amber.engine.common.virtualidentity.{ExecutionIdentity, Workf import edu.uci.ics.amber.engine.common.workflow.{InputPort, OutputPort} import edu.uci.ics.texera.workflow.common.metadata.{OperatorGroupConstants, OperatorInfo} import edu.uci.ics.texera.workflow.common.operators.LogicalOp -import edu.uci.ics.texera.workflow.common.tuple.schema.Schema +import edu.uci.ics.texera.workflow.common.operators.source.SourceOperatorDescriptor +import edu.uci.ics.texera.workflow.common.tuple.schema.{Attribute, Schema} -class GeneratorOpDesc extends LogicalOp { + +class AttributeUnit { + def getOriginalAttribute: String = ??? +} + +class GeneratorOpDesc extends SourceOperatorDescriptor { + + var attributes: List[AttributeUnit] = List() override def getPhysicalOp( workflowId: WorkflowIdentity, executionId: ExecutionIdentity - ): PhysicalOp = { - PhysicalOp + ): PhysicalOp = PhysicalOp .oneToOnePhysicalOp( workflowId, executionId, @@ -23,18 +30,26 @@ class GeneratorOpDesc extends LogicalOp { .withInputPorts(operatorInfo.inputPorts, inputPortToSchemaMapping) .withOutputPorts(operatorInfo.outputPorts, outputPortToSchemaMapping) .withSuggestedWorkerNum(1) - } override def operatorInfo: OperatorInfo = OperatorInfo( "Generator", "Generator", OperatorGroupConstants.CONTROL_GROUP, - inputPorts = List(InputPort()), + inputPorts = List.empty, outputPorts = List(OutputPort()), supportReconfiguration = true ) - override def getOutputSchema(schemas: Array[Schema]): Schema = schemas(0) - + override def sourceSchema(): Schema = Schema.newBuilder.add( + attributes + .map(attribute => + new Attribute( + attribute.getAlias, + schemas(0).getAttribute(attribute.getOriginalAttribute).getType + ) + ) + .asJava + ) + .build() } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala index 3e1074f331d..819f2bf2f9c 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala @@ -4,24 +4,16 @@ import edu.uci.ics.amber.engine.architecture.worker.PauseManager import edu.uci.ics.amber.engine.common.InputExhausted import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient import edu.uci.ics.texera.workflow.common.operators.OperatorExecutor +import edu.uci.ics.texera.workflow.common.operators.source.SourceOperatorExecutor import edu.uci.ics.texera.workflow.common.tuple.Tuple import scala.collection.mutable -class GeneratorOpExec extends OperatorExecutor { +class GeneratorOpExec extends SourceOperatorExecutor { var iteration = 0 var buffer = new mutable.ArrayBuffer[Tuple] - override def open(): Unit = {} - - override def close(): Unit = {} - - override def processTexeraTuple( - tuple: Either[Tuple, InputExhausted], - input: Int, - pauseManager: PauseManager, - asyncRPCClient: AsyncRPCClient - ): Iterator[Tuple] = { + override def produceTexeraTuple(): Iterator[Tuple] = { tuple match { case Left(t) => buffer.append(t) @@ -29,4 +21,8 @@ class GeneratorOpExec extends OperatorExecutor { case Right(_) => buffer.iterator } } + + override def open(): Unit = {} + + override def close(): Unit = {} } From a0af4887fbbac702bcb8c19c9749f3bdfdb427f9 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Tue, 13 Feb 2024 16:31:13 -0800 Subject: [PATCH 10/29] fix format --- .../controller/OperatorExecution.scala | 2 +- .../architecture/worker/DataProcessor.scala | 5 +- .../promisehandlers/OpenOperatorHandler.scala | 5 -- .../promisehandlers/ResumeLoopHandler.scala | 34 ++++++++--- .../event/OperatorStatisticsUpdateEvent.scala | 2 +- .../operators/loop/GeneratorOpDesc.scala | 57 ++++++++++++------ .../operators/loop/GeneratorOpExec.scala | 35 ++++++----- .../operators/loop/LoopEndOpDesc.scala | 2 - .../operators/loop/LoopStartOpDesc.scala | 3 +- .../operators/loop/LoopStartOpExec.scala | 28 ++++++--- .../src/assets/operator_images/Generator.png | Bin 0 -> 76268 bytes 11 files changed, 106 insertions(+), 67 deletions(-) create mode 100644 core/new-gui/src/assets/operator_images/Generator.png diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/OperatorExecution.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/OperatorExecution.scala index 6dddff7b79e..2f384e58068 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/OperatorExecution.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/OperatorExecution.scala @@ -48,7 +48,7 @@ class OperatorExecution( WorkerExecution( id, UNINITIALIZED, - WorkerStatistics(UNINITIALIZED, 0, 0, 0, 0, 0, 0), + WorkerStatistics(UNINITIALIZED, 0, 0, 0, 0, 0, 0) ) ) } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index 5932166f121..29e3ace0406 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -22,7 +22,6 @@ import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{ FinalizePort } import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.PauseHandler.PauseWorker -import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.ResumeLoopHandler import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState.{ COMPLETED, PAUSED, @@ -33,13 +32,13 @@ import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerStatistics import edu.uci.ics.amber.engine.common.ambermessage._ import edu.uci.ics.amber.engine.common.statetransition.WorkerStateManager import edu.uci.ics.amber.engine.common.tuple.ITuple -import edu.uci.ics.amber.engine.common.virtualidentity.util.{CONTROLLER, SELF, SOURCE_STARTER_OP} +import edu.uci.ics.amber.engine.common.virtualidentity.util.{CONTROLLER, SELF} import edu.uci.ics.amber.engine.common.virtualidentity.{ ActorVirtualIdentity, ChannelIdentity, PhysicalOpIdentity } -import edu.uci.ics.amber.engine.common.workflow.{PhysicalLink, PortIdentity} +import edu.uci.ics.amber.engine.common.workflow.PortIdentity import edu.uci.ics.amber.engine.common.{ IOperatorExecutor, ISinkOperatorExecutor, diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/OpenOperatorHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/OpenOperatorHandler.scala index edb62b43c41..cee60616000 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/OpenOperatorHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/OpenOperatorHandler.scala @@ -2,12 +2,7 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.OpenOperatorHandler.OpenOperator -import edu.uci.ics.amber.engine.common.{ISinkOperatorExecutor, ISourceOperatorExecutor} import edu.uci.ics.amber.engine.common.rpc.AsyncRPCServer.ControlCommand -import edu.uci.ics.amber.engine.common.virtualidentity.util.{SOURCE_STARTER_ACTOR, SOURCE_STARTER_OP} -import edu.uci.ics.amber.engine.common.workflow.{PhysicalLink, PortIdentity} -import edu.uci.ics.texera.workflow.common.operators.OperatorExecutor -import edu.uci.ics.texera.workflow.operators.loop.LoopStartOpExec object OpenOperatorHandler { diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala index 93c225d4b86..785b2d8b07c 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala @@ -1,33 +1,47 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.EndOfIteration -import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.ResumeLoopHandler.{ResumeLoop, loopToSelfChannelId} +import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.ResumeLoopHandler.{ + ResumeLoop, + loopToSelfChannelId +} import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer import edu.uci.ics.amber.engine.common.ambermessage.{DataFrame, EndOfUpstream} import edu.uci.ics.amber.engine.common.rpc.AsyncRPCServer.ControlCommand -import edu.uci.ics.amber.engine.common.virtualidentity.{ActorVirtualIdentity, ChannelIdentity, OperatorIdentity, PhysicalOpIdentity} +import edu.uci.ics.amber.engine.common.virtualidentity.{ + ActorVirtualIdentity, + ChannelIdentity, + OperatorIdentity, + PhysicalOpIdentity +} import edu.uci.ics.amber.engine.common.workflow.{PhysicalLink, PortIdentity} import edu.uci.ics.texera.workflow.operators.loop.LoopStartOpExec object ResumeLoopHandler { final case class ResumeLoop() extends ControlCommand[Unit] - val loopToSelfLink = PhysicalLink(ResumeLoopHandler.loopSelfOp, PortIdentity(), ResumeLoopHandler.loopSelfOp, PortIdentity()) + val loopToSelfLink = PhysicalLink( + ResumeLoopHandler.loopSelfOp, + PortIdentity(), + ResumeLoopHandler.loopSelfOp, + PortIdentity() + ) val loopSelfOp = PhysicalOpIdentity(OperatorIdentity("loopSelf"), "loopSelf") val loopSelf = ActorVirtualIdentity("loopSelf") - val loopToSelfChannelId = ChannelIdentity(loopSelf, loopSelf, isControl = false)} + val loopToSelfChannelId = ChannelIdentity(loopSelf, loopSelf, isControl = false) +} trait ResumeLoopHandler { this: DataProcessorRPCHandlerInitializer => registerHandler { (_: ResumeLoop, _) => { val ls = dp.operator.asInstanceOf[LoopStartOpExec] - if (ls.iteration < ls.termination){ - dp.processDataPayload( - loopToSelfChannelId, - DataFrame(ls.buffer.toArray ++ Array(EndOfIteration(dp.actorId))) - ) - }else{ + if (ls.iteration < ls.termination) { + dp.processDataPayload( + loopToSelfChannelId, + DataFrame(ls.buffer.toArray ++ Array(EndOfIteration(dp.actorId))) + ) + } else { dp.processDataPayload(loopToSelfChannelId, EndOfUpstream()) } } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/websocket/event/OperatorStatisticsUpdateEvent.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/websocket/event/OperatorStatisticsUpdateEvent.scala index 905c73ededa..5ce0715934a 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/websocket/event/OperatorStatisticsUpdateEvent.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/websocket/event/OperatorStatisticsUpdateEvent.scala @@ -8,7 +8,7 @@ case class OperatorStatistics( aggregatedDataProcessingTime: Long, aggregatedControlProcessingTime: Long, aggregatedIdleTime: Long, - loopI:Long + loopI: Long ) case class OperatorStatisticsUpdateEvent(operatorStatistics: Map[String, OperatorStatistics]) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala index 13a4237a256..e83f7bdd9f8 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala @@ -1,31 +1,56 @@ package edu.uci.ics.texera.workflow.operators.loop +import com.fasterxml.jackson.annotation.{JsonProperty, JsonPropertyDescription} +import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle import edu.uci.ics.amber.engine.architecture.deploysemantics.PhysicalOp import edu.uci.ics.amber.engine.architecture.deploysemantics.layer.OpExecInitInfo import edu.uci.ics.amber.engine.common.virtualidentity.{ExecutionIdentity, WorkflowIdentity} -import edu.uci.ics.amber.engine.common.workflow.{InputPort, OutputPort} +import edu.uci.ics.amber.engine.common.workflow.OutputPort import edu.uci.ics.texera.workflow.common.metadata.{OperatorGroupConstants, OperatorInfo} -import edu.uci.ics.texera.workflow.common.operators.LogicalOp import edu.uci.ics.texera.workflow.common.operators.source.SourceOperatorDescriptor -import edu.uci.ics.texera.workflow.common.tuple.schema.{Attribute, Schema} +import edu.uci.ics.texera.workflow.common.tuple.schema.{Attribute, AttributeType, Schema} +import scala.jdk.CollectionConverters.IterableHasAsJava +class RangeAttribute { + @JsonProperty(required = true) + @JsonSchemaTitle("Name") + @JsonPropertyDescription("Attribute name in the schema") + var name: String = "" -class AttributeUnit { - def getOriginalAttribute: String = ??? + @JsonProperty(required = true) + @JsonSchemaTitle("Start") + @JsonPropertyDescription("Start of the range") + var start: Int = 0 + + @JsonProperty(required = true) + @JsonSchemaTitle("Step") + @JsonPropertyDescription("Step of the range") + var step: Int = 1 } class GeneratorOpDesc extends SourceOperatorDescriptor { + @JsonProperty(required = true) + @JsonSchemaTitle("Iteration") + @JsonPropertyDescription("Number of iteration") + var iteration: Int = 0 - var attributes: List[AttributeUnit] = List() + var attributes: List[RangeAttribute] = List() override def getPhysicalOp( workflowId: WorkflowIdentity, executionId: ExecutionIdentity - ): PhysicalOp = PhysicalOp + ): PhysicalOp = + PhysicalOp .oneToOnePhysicalOp( workflowId, executionId, operatorIdentifier, - OpExecInitInfo((_, _, _) => new LoopEndOpExec()) + OpExecInitInfo((_, _, _) => + new GeneratorOpExec( + iteration, + attributes, + outputPortToSchemaMapping(operatorInfo.outputPorts.head.id) + ) + ) ) .withInputPorts(operatorInfo.inputPorts, inputPortToSchemaMapping) .withOutputPorts(operatorInfo.outputPorts, outputPortToSchemaMapping) @@ -40,16 +65,8 @@ class GeneratorOpDesc extends SourceOperatorDescriptor { outputPorts = List(OutputPort()), supportReconfiguration = true ) - - override def sourceSchema(): Schema = Schema.newBuilder.add( - attributes - .map(attribute => - new Attribute( - attribute.getAlias, - schemas(0).getAttribute(attribute.getOriginalAttribute).getType - ) - ) - .asJava - ) - .build() + override def sourceSchema(): Schema = + Schema.newBuilder + .add(attributes.map(attribute => new Attribute(attribute.name, AttributeType.INTEGER)).asJava) + .build() } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala index 819f2bf2f9c..72c9ea13343 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala @@ -1,25 +1,28 @@ package edu.uci.ics.texera.workflow.operators.loop -import edu.uci.ics.amber.engine.architecture.worker.PauseManager -import edu.uci.ics.amber.engine.common.InputExhausted -import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient -import edu.uci.ics.texera.workflow.common.operators.OperatorExecutor import edu.uci.ics.texera.workflow.common.operators.source.SourceOperatorExecutor import edu.uci.ics.texera.workflow.common.tuple.Tuple +import edu.uci.ics.texera.workflow.common.tuple.schema.Schema -import scala.collection.mutable - -class GeneratorOpExec extends SourceOperatorExecutor { - var iteration = 0 - var buffer = new mutable.ArrayBuffer[Tuple] - +class GeneratorOpExec( + val iteration: Int, + val attributes: List[RangeAttribute], + val outputSchema: Schema +) extends SourceOperatorExecutor { override def produceTexeraTuple(): Iterator[Tuple] = { - tuple match { - case Left(t) => - buffer.append(t) - Iterator() - case Right(_) => buffer.iterator - } + attributes + .map(attribute => + (attribute.start until attribute.start + attribute.step * iteration by attribute.step).map( + i => (outputSchema.getAttribute(attribute.name), i) + ) + ) + .transpose + .map(x => { + val b = Tuple.newBuilder(outputSchema) + x.map(y => b.add(y._1, y._2)) + b.build() + }) + .iterator } override def open(): Unit = {} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala index ce6aa1c841b..be2ba1ac559 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala @@ -1,7 +1,5 @@ package edu.uci.ics.texera.workflow.operators.loop -import com.fasterxml.jackson.annotation.{JsonProperty, JsonPropertyDescription} -import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle import edu.uci.ics.amber.engine.architecture.deploysemantics.PhysicalOp import edu.uci.ics.amber.engine.architecture.deploysemantics.layer.OpExecInitInfo import edu.uci.ics.amber.engine.common.virtualidentity.{ExecutionIdentity, WorkflowIdentity} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala index 34fe1fede4f..54a958bc1fd 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala @@ -52,7 +52,8 @@ class LoopStartOpDesc extends LogicalOp { override def getOutputSchema(schemas: Array[Schema]): Schema = { if (append) { - Schema.newBuilder() + Schema + .newBuilder() .add("Iteration", AttributeType.INTEGER) .add(schemas(0)) .build() diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala index 9bb986e4d97..625ac545aca 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala @@ -9,11 +9,15 @@ import edu.uci.ics.amber.engine.common.virtualidentity.ActorVirtualIdentity import edu.uci.ics.amber.engine.common.workflow.PortIdentity import edu.uci.ics.texera.workflow.common.operators.OperatorExecutor import edu.uci.ics.texera.workflow.common.tuple.Tuple -import edu.uci.ics.texera.workflow.common.tuple.schema.{Attribute, AttributeType, Schema} +import edu.uci.ics.texera.workflow.common.tuple.schema.Schema import scala.collection.mutable -class LoopStartOpExec(val outputSchema: Schema, val workerId: ActorVirtualIdentity, val termination: Int) extends OperatorExecutor { +class LoopStartOpExec( + val outputSchema: Schema, + val workerId: ActorVirtualIdentity, + val termination: Int +) extends OperatorExecutor { var iteration = 0 var buffer = new mutable.ArrayBuffer[ITuple] override def processTuple( @@ -29,22 +33,30 @@ class LoopStartOpExec(val outputSchema: Schema, val workerId: ActorVirtualIdenti iteration += 1 Iterator((t, None)) case t => - if(iteration == 0){ + if (iteration == 0) { buffer.append(t) } if (outputSchema.containsAttribute("Iteration")) { - Iterator((Tuple.newBuilder(outputSchema).add(outputSchema.getAttribute("Iteration"), iteration).add(t.asInstanceOf[Tuple]).build, None)) - } - else { + Iterator( + ( + Tuple + .newBuilder(outputSchema) + .add(outputSchema.getAttribute("Iteration"), iteration) + .add(t.asInstanceOf[Tuple]) + .build, + None + ) + ) + } else { Iterator((t, None)) } } case Right(_) => - if(iteration == 0) { + if (iteration == 0) { iteration += 1 Iterator((EndOfIteration(workerId), None)) - }else{ + } else { Iterator.empty } } diff --git a/core/new-gui/src/assets/operator_images/Generator.png b/core/new-gui/src/assets/operator_images/Generator.png new file mode 100644 index 0000000000000000000000000000000000000000..d77af3e7735a45db7d5a2d9c3b0fd10e5b1ccb48 GIT binary patch literal 76268 zcmeFZ2Ut_fx;Gwmt3jlxND~kNl@8Lof`SkM1*C)~gcf?Q0&b0<5u|GD?v`+d&y0K;0dW@i1?%scPA@9&+XzN2p- zdQCMAHPEqRpyNizjX#~)N9o$9n%5qiRmCJBz#VdxATB2HRsy6oUy8#|HdI8${umDGxto0RTrOOI_ za(>Qk&Ng0_m;IcbTs-Cc6tDcHaycOX{j$iF%YP~2<*0bYNbCM(RaXz2%TmIk!lGAz zr#-A~<#cbW{oAv^e~MTBt&_gKzQVrZ!mb{6B4V<#vLd24L~h&=0!j#Z`nz~p`U$ys za{qe`w{1LO9&k4=xU0+M?=@Olxq5pkUIAMEOP8!+f34Nc+r#NEby~wjY@BSIZCt!O zf!7xk`CgSZOwQBW%E1Qa^>01XvvC&zvLb(XL*#o4ziaV(f$x`q*Z7|OD;`Rhf2N-Z z{6yd<0zVP>iNH?;{@+93ZyJ@23!p~%0{Rl@hzwE%ojCqIegAy|NWYx=<$D63I(70C z75K~Ir{TJp8n_~h#GwQ$>}G@jxm6a zK7%+wfR=as#Ia+?K>wb=R42}yJazh)f?I;Pe_Ci!vffv>~_%%y@q#7My4BUHf3q3(Ozn{SFw+}zwyMP|K>cTx{{k#0g~Sbv z>QI`cvQgCKs5B{}q(G&oiv8n+D-&tSnyC5!&w4nl56NB15d7r-+aTrqf~ODdBUoQX z?l!oDIt*nu3s}bIIY!@@;(vgw^Dp3=EYH^V^Uc;r_{P*4smad-XVpl2s^ZOXBD))O z-jQ_$H~D$Nm>~u^?|g=}L;{^Uolr*m9q%K3d&-R2uNY=pNX=$T4ejL(ZOZw@LIT{w zK2>8`Z2H%n{A0JSy|juA8ae-2*kx{YZ+Fu+Q<~E*(!O#1cdyWD7ZMa^@Ep$>Xx@_} zf4)7FrzX-%?j6ka>SChsm&}BM7=dVU@YpAHE_jW`N|0qb6ja zWB5rNoBT(h9ZyP1kr>(@m}sva5+!g%stL?_2(vT7lDz=Q+4j4R{y2MDry~!&5two> z$^V^1>|19yrz0F zJ8>6@E~guOAg8lhbWe>D`0Y%x6Q|$s;h~ySBU} z37Z_+dnw6YW^e7S%7e5}JsIF-#{qq$)LxQ<`#8$}F~h>b*ue6)-?#O{{Q7|$yn)MM zGc^&S0af{&;vCkeQ4~wX4eqJR=lq=&DuD-461ZzBbeBLk@jq=WE_4X8YtxoPe zRh+3H>U1vlE`GQk=~u;x)fGiZcM>AS7Mw!AsD8fr`?gPK&yD7kMtfVf7K_(LiD{&0 z+t=Syr_NOv@feekTJ6jhR35N*CvwOed|HOn}&YZ|%85raAcE$R1{?cZ1kYbX`} z`!*>_S|uNvD>G+3h%(0d`JQpE>F8cjOB89@B^Sman?g&d)Y$GIjE1&+HaTl^srSir zPX2Q{!Tbk(y ziW?T~%Ci-MHLMX<(JbN~d!{+#aP2KQ$<$xlvuTQgwm^h4{_&+9#7`dHG-pZT0SQl6L)cPmRZUY26#ku2?hsrLl zAOq#Onf1|}r;b21C)1w%^Wpz@cTdnv4%KqnnIA5t@wJs^NJ66tg@ULCC)obwo?d9u z4RiRs&f5kJUncSHlFwpiVhU!|?C}>CGS&UnjD5Dasu5TFS4d3JPvW2vOCfeNj$$&l z)dc9Y$jlnPbZz_`;@!YV7g}`WK`Fv7m9r;VL3h7!%*-})e5H_oM%ZbG8S;! zhs%tD^$|*bx2Qh)Mu6e+7u$N!MS0O~&`!N_;&u{SMGAO-{}s#_S_`Vtpng>B3#`mv|P1LVqq zgTDPkjOd|%CV|5D=;Ws*ftfrqReP{&$7Xj9w|~@_?x;aYD%jnGOazm_zT0y;g!Nn2 zzx}S-5)%lcE&YQrTpy2Qr7STm*Pt|M4Us?z3BB>~RVyG?^@#p(OM|N1|p1 ze<4$a;RrOzIriZHwCbw!f0 zSII}wZKCAo4nm=#5#d$Ln`Jc@8A=MH*R;@p)}vKHHHQk$E7tq8`19)inrpF+18?GL zO6_&D*+M+?SH8@Vj>=nK#I*2zhxfsqVKk*^o-PPTL1%t*m0Ci}6zo?(EBzZ3!c~nY1K16d_V$bOiBe z=`sieo~>jXgk$M=RC~VkzBq|p;tba9c=w_qIQC)QlOKJs-`zjcG*q|W*zOxECi3vk zgWp8R9^>VNtK0J<7B`H)7h7|NZ!Cw-jr_LIgf$uPFsNvl2@GX(Rq)Fm?xGZ1M>W-s zM>PpTr=}B^F%oYQ-0!U>t_Ex;iz7{SynQ{P&KsSr5BFq(!!B@vZ+-e+DV2Q1 zLGDHAY?s89_(3bi)EG}ilA!n{9P$X%zwDqzWnGStr%z67NBA7(?b1AjccF4+9Xc`R zH59lcaH@sO>V-@*fZN<_h-OKmBMikgZ|(T`cn5{9jt{Q$*|JVEtM}&jX8wcer1jFs zq18;)*Z8=s_aEAr7AxEbVbPDhm-F8WJ^|`55ME41DUE%VJ#j~>drRl#up!9F;jpfUfE!#;+cnpO)0Ec)vJA(Zp0<#$t+GX-*kjk!Q)$8+ptz zujkKWxkhApa|Duf8Mb+)hezLQ^Wiq9M=FkzXP->!w!by5Q6#2lk;mO~c z&-<6k4{cpDacS8azBV07QS@N@)ic)BvLP_Jg63nz*Rq1qnpV@$Z>EzbeQv|kd8%&0 z9=QuLuAaSI7!@GIetQC_&O7p2ODWFYKCduhBBXki$!HoGQjhkoI^3}6pa_m?{Y-SuA=u{IUf zn5rbK$~!3~ud%^ID!UMc2^1Bvs3oWA*cG;Wx#)tN5=-rKCYA>JPO}j3V!x0qm!Kl; z{Xd9H$ec}Hi)`i)-ZuZ4b>OAOOHI7h(e-mv0p{aN4=|$rHDMe>N1#2PBhWvcs)f55 zRe?!09Twhgwp7(*nk~xt+%mfmyYuCfX5{8V13%rC0NT&?Ko7V2eE4`)(3}&=ch!^| zFMBywVzW42+hO4^?5Ov#{(9kqt3pz13I_eg&ZB0S*U9G`m~&lboKLG8B{tdS9Qkk#Lz7Ps*ZyI}h` zM8kicx!Dj$Vo8x)Z0>B*aShN=K6m#qs-E8aPv!Ku${x)fMEIG$>UAeFl_Q>GQhj2fmHBXT z9%zy~ea6*2Jx1XToe8%&&QQ1Rt1LUIc-dy}UN6d% zCR7?>0jW#zw}CRkTj`!T$uoonGmM7?k$_J8F)3si4cSQ8WnWauPxgBGO2r7z5KY%$ zPXZ5FF;;cWYA~b6awCT^8*Q$sWY+mXtf z{7(I){^f@>ugCoE{;?t8%0DVJ$EBBEz^`RSe((o2M5*qqhVeBqq5@iP`dKn6F&Cyo zM;KWP4@mwWwS~UaoF5&y-J?n2B)C-t?o0DOD#^=8b=PF%BSCZ8GIX4-jzyAi>Q>X0 zarV8@`&FcvfNQ#otZN~0{zXx@=gd;gb=H=p9?T>`iXEE{&8EI8p3n4&@K+{Q_p876 z7H-eKw&R;{eM1{NAUSaa5?;6Ac-d8@R{d^r*eSJL?oaBx^Uwq*8-4xTP87F&9N#x) z19sh>fl~1oYAqc}e5tH&C~~@5ES6xxD~m9@*B&K|ENp?dd$*5jw1lP{`&3sNUg?eHAIS}O~M5tD8F7E1|(E|P`#-Ht&1=?f$dnd-zpX$J)Q!-O=D zT13T6kjF-%TaIPXn2Tjg%Y@C{q z5SXh%+c<>{uDI2T67M>^k@E<2Y#(U}Rue;Vf!*<9j+yzkF;g9fC9)I=z5Jz8pK@fM z7Sd-CaUFUDvY~mi+z=$#ez1Jv6eNJrC6AAM9|fO#n0!t{uz<`=VqQRSogzX@X1+#- zZqU3bcGiyJ>;5F_7+`oG@P~OzRPC;gZ9B9on8yI`7@3e#gU8%oyRGOH;Rk?wI>(_E zfT;VK1{$i;B(0~Tc^(3}0GIP~f&Do){@*n#&4^GXWLMhX3mNl+G z0wWI6?-!PRnuv4hgBS)b#fI$Y(0vR2sKS5|MR1jhjXi@UX-c@#T$sb9CZ;Nabt^+@ zG!K{9WDbU>o&86piQul`BhbLs3g~p)ykgp%+@-ct0UZkKL?KhoQ{~A$3`nlJie$4% z7QaK*XbrmIo~)XH-iLFhgAXxSXeS=4F%{WIl7Inz2O@JF=(QYTCQr`1r|R-#<<{_` zCG3SNOI;|%sUXGiAg?K9MN6T0B5H?xUWFbZrE=+h$jyJ$@&N#@hEA!KF_r{(Y+edW zDD=;?zBdasas2S`-JSqj-vTb`;nKe6~h`9ZeQy^<7@ zyAM}$R$maoYWYJ<;!r4FUS{-+7mRAld3kww#PNf6MyVH1Y07VcxM~y|*AhxUQ+gQ# zA7?(?F4}xK_xua(!*%~kb))1DrcWj`-yP(Smk*3VW@;y^oD=n zdlI`Pi$m?T7bCh=nK11e_~IfxEQ_U7AEA&Xb^l@FA1rO#t4M9~$$D4!@UN)O6qu{- zR8FV5HJ2a?Z;uz-`K?DD!ZI+f!KzI%-kMA6Y>gVD7_K0P;x%;B+EJX9W(&%mStr^g zgWET?1mIQNGfSH8$@hFO6_@0LeLn=IaT~Y}X`d%hq^!$Qw0nkQOIUbONqk&9C?d^k z1Q+mg`3ne?S2G~Q-ymXdAu#j)>(sd++d-&wnXZb0dvOT@@X~H`8(?J`8XL7930-rS zzGFWB(vBo@!JSKM}bE)zVN|KuNalanu&)Mlh_2iVtk2r2SS z2nyUFhCV37v@mszOPAyQvs}xnxM^Fygq)1}fxx+E2t~{V`l|#w>>ek|H;oo4P4jAc z2G3%X8N!yrKTG6)7kQo~`6lWmt2t3GNML;2AVu74pk7`kh1ENi<-5r1PR*qSp1DqpkI?aAfAxvMQr$@2~lurV)%rp}Yd>=e01-_J5m7fQdJ&-Ba3mB%Fr=p*J+Y|de$Jr`_JoE=@Y&*@>c zjhqlgMB3}Bu%$oi&p$Vx0am(G9Xh%P4~m~fw3k!Zn$_v{NN{F#>*0BCiR+7TlL#D1 z`XJZuiMBIgku8|nQC&}F@cziaL^W&8#I^o1e|M8T))46$OxDU1&`E}+0=NO|?Vg4U zAB9|Cx&)s72cP&FTrZTQlww^(!iKIPmA&YjPt~&}?h4F3$kNtq$a79rI|5xwk&^eP z!uCrY0vJgV>gYU&opw4N0k5O3)iyk?b>068N2yx%O(7Aty)|^qz8c#`R<75vm2p1~ zy0>oO)h9R`oD0v&9U{Agon83q1H9oRnm1@TtdZI2LC?%o>iIbmW_I?pdQSL#DgeEcKU|E6ay+{!I2t7s{GEt%ZmVxJ9_w8?)fA~e&=yhv7i zezzUFR~9qr50-uWF1FBgH9_=o!msTSrmAeUDPd$tk=S8J&I-ziU>Cp;*|+~X+{XFT zOq__l^c@!0JN>r9nm+N25pw5S!7Aqsb8*g4oYMkHVijXgnDj09V~0SD(N!Tk;qRCE zt<1AtrQWnCog}zNYOVIx)>;<<#TjF(;A!v5J(O;?E9QQ-R1$b$h+mqmmsiH6T&w6H zuhfe_%-oyMe?%QIXxQF697Jn-z-wyalpSCA(kXXtVc+3mhkL`$^jp{?5G|A3RQTC9 zF=9ERuO6b3(y1-YcZ)j-*3!ijNV3#^m6T*uYenf1NNM_UpxjujU8Z-Wl{C|)ZL+*A zSvSqLpA*B-zn2J>Z+Rx+8;H#nomjkN1Yz!KW^T-lX-7yc^O1DfcE;CK%r;-wwE`${bFt{c0S4@gM{(T?!q!MBr%X-1 zCaKnFC#i-BchKg+ea|G*UvsvQxI1#|57433xUyUYZk!G4dVPe>P<5ub^$Rry1UKzx zi?%<0rem*`#Lzv(ZOeKX<%RBS&f45pu=q$`_ajh(udhM%eqhi>Cthq}zwq(pn83IF z+MQDKv2Wn!O@bRM6-#1wHKeWJnGY~G_V?WpA)b!_ThbrW4{Ycs&$-~>9HyJOwR}&> zy%dOPN|?!`uI;UbOt4z6k<+qMa$4KL7wccRnDKz#j2DWKyxd+bVVG4LxmIH0r(5|B zjD0b*FK$qAI3O1Fi9MP~H%Pe&`bB=opd&}ZHOpKQVPYDXbm&=``lJn5WyCU+`sYIk z+OA_M1?NZHiFYhQYCP$#D0ke+$ zYaFdXf-6XQPC+)!0X1=GJ4SKWgK+Tn<{tnAonjmA%pXhNxRcGk>|(fz3$UuEdR}pB zC?QTE;YC2~?>S?CeOTa{&k<;y>CUkJ=bDF%YESB%iIL-%62|j`9yeJ7rFO)eG<#3g z=Zl5+Q%bj_HiaFvRx|*@iTx1>d?zO|Uc9CS<&~M>xj5wNof!pVG9({V`4AN~QbmqH z?2*6G?)>KTt2jg!1Z^+Gr3$eH z26%M*Wwk&-Xi)W>Jb=F^9&}7Qp?JU`R34j%eJ{_JyLmQ0RO`iXXIvq5_J_Sbtu>-% zVqsX*L(`igCp=Eyv3Rmuq~Ah8?-3VPUv<}#B@{P=GDB=MIVF5?B)avC@5ueVS?gk4 zdC(3-GnkuZ_n|>0=nTt{#S*DGSgYYcljE6WoXh>yqTNRW9>n4qzsw+d+rNVMw+ypu zpmyD>g!YR$zBLsVc+zY*`0B;U(>^YyYc5=KhRuH~`M2B;e4fV@9rx0bv6a}_)1tb- zs#n!4zpRx;_8Wyx+3UvHH8e_C^}78sI{2);ZWLXjJ4H_L({V(t1SYBnNk|c?Ullw* zPX2dMa1b3*HJXUTe}$1o%SfkzXKS9~kpKjgw?<2Xfc8=bF+i(@=V* z>nj)|7KdynvpB=m2R}geGMjif?$cEr`;_l_Q8sLHUnT_Nu^gf!NT#D5g6|7rk zmU3)ArHX404);ip66AwuM_SZ7=Wq$uL~O*2Qq>{JR*T<&_x^ih1Jrz!GAYEjjg+2! z2%Q04xAP9Ek`~*MrrW$8aJI*+LKRP(eFi5AcczSCH-JI#$7ETvD0j&3dK+3eyfqoZ zCY>&LIo>3Oy}!b1DTvS{eJ<(lDnMmkosGTs^K zvw(CF4k?PWBZ9RgEY~HQG4KqGc&h|VqMghPn3wCG#E>y^e4(+DWncC4az_p6h@Sd{ zW$8Au@K<`WHbdAa);B25ngV*@75}Ll_jstU;U3PDGa4{$>4yDAvLVMPIMv2WB^E8p zaqwH*W_j{Qkp45o$b09W2p)o>qwC9WQWS0q!*D#X>Dx#!)%bYKDKcww1iaW3`QRFs6Yjb~B1 zfUhBIZHC~;uD)e8mMUeKtgwnyCNkayl^%Rvynt`GKAkH*Om@nAHPrD@kYCO04TWFy zIh3r#tmZ$fI*n33-U590f4&l55U;EoN{HW22-}TOJmn}nm@;m&@u_$+4&Jt^yL&E+iK*@{=UE=uq@XuG8A51KPB2mv{KQ^)SC#l2P~W5`IzWYMkv*ky@Jc5oq7? zV)NETgKg)CT6Q}ff0dq?7(U2u5d^G4e@OpK26Vz=v6Du?Tfb*oTFcp+wJ+EpLZJ#? zq_oB9S+BI%xUyH=ZFvN8Vt91w6DC^qP9iO!n0@3IpwRsH@`f zB}Q7yTZ?G%Wxu_SdiLq^P2+Y8K7YMr=SsVWNsMc8v9eR-bnm}jbX|}reBYl+Wz%8s z5D8;{jSx$4Q9g8O2pjWr5ptz&7N9S!PGMenZW2(`g%aDDL_XTR2|C6utr{31kuvcj zpr3UWR9w@r{1vLV%Iw;aRni8!-!gXu@^OK!xp2pazoHp@_Lgv>nq zGwIO~IN-gnKm4KuR+(2{>1cO4-P%Wh{q$CcRPu({dpW&_9oo@1 zwk(hZ$e^!lyBZFJsc4tIgEt{^OSKMhvGd(!AD6_1?UQ3*U;=G}FAAWR<#eZ`7{Z8s z{=!04L2qWeGSAZ8@|$dw(W3K=7`~>4gwY=MK?L`>_r}J{-B#X(FODcfHm}igoL>#9 znw||#8*Qi$7AbptAkvj63zEP*^Hae=zM6L~vo729eaBXbQ zx3%Lyqah5jZ{f1DYmq7Qv&#P&SPE_>l*_un$EgpFjd@Lu^|Q*5WXBITzHL0ts1nbX zl4Rj&uCSA}nN4L@@t-&DEJtw)a%=jhBb9x+V%n3-cX4o!0I41Bd$Pe(Yo(;c*v8L7 zc`|$S5pq?1i-8h2h(bZKMhd`@apDFqA|4$+0fBy?sDStK@hHppLwa7%qB<%f*s2Dl zWVF&O*%KTF@fS)%1UpOmk4gJpk#OFI=b8z&y1TNX-%3+ee4avDE?Yu_V&LxkqL}nu z&Pj74(1{PTzDj*FWq11X@0KGw>O;-yg$u#;_NzLQa_-2Y$Lm@(Xih(GZkJjGp$t# zZ5I}YaJKinc5gnj7SopCoIALJ3QC%tK>Ff{EA{qj45nA7w`PV)3pdl+&Cz_uqyC4_ znOouB>NfI^Z8a>HQw5V=3IN^=)TIZZeMFqKCDp*cLd~S3?$I$Nopz`}w3;YQX$ZT2 zjTu)=KV~UW;+n@jjI<^fCB7SfXRSn2IHrGC$d*;v0FSJ}pMT>TkUZIx%3@PiKd0vS zwJ2oA1qN0xpN>F%0ic;WIFE1Mt9= zxFGgT#k#YFVOG@o-6mf1Dwulx(y#qR@EgMJ*+m+HuJiR$anTD$ptgMW*JS2H#NO3y z>7ke=G~D)oJ2q$vkn$^lqvQvBNQ3mnU=(+#{W!OM(ZJY^E`k_;c`iw@E<$bCY@ym> zI+O0(hs#WKZK1ku}h z?`bl6XlJik6XkFk?#W^fQsN6p%hG|Ut!}RUj*cUcH^&pu3G`Rl>`PyEQqQ!=IvnPr z1)iC8)D)Lv*`xKc0q?od5vUAScdA*Az2jthY_VQU@f*9E=L&>p17l(+&Rx&03~=kU z1Dq@Npk?e|p+x7Ag37dWydds@?-fDA{Z?;_jI`9x;U|&&7}wF1f3Zg(kL<1v^Nz*Xu=tK7xM0Pkf)$L*hAPn}>(Q31u%Y z-f&@Ye6C`oRcd6IY~hp1{J3sF1EA-|Qr~ofedvj(jG4z<|n z7y$L8@?6tNujP{>D#|Ty%)&vnzq0UTraBZ zJ_6k~N-Uac)*QGo9*<=?*kW|m<=oL9r)~7dM%is}0pDDINY{U^6`=H!6xQ*8YY^DM z;1%Nm-{AL;`?o|zuBgbzZv86Go)@<9_C>3SdS7=lPielcJnWfiE3@hl7w5s-)a|xq z^%2$DssP5Bq5B!0&IX#rSwyPYRvsUsL}6&38pCqEs5R(En(|``rF7dn0?*}Zps6ne z*MX+36ReAfg4_TreVYp`8P&-VP|@KNyB_>HZwezmb;)6w9&z=6ivJ-7&a;C>-yqVx z?ff)U%=>zNRu$4OuikZiFMnjgp{FIS?vO7_b%=W}4bgr;!sG~k7%i3R3o0%^N96Kd z??iAF(W8x2=#N15N>4ziU(QElFVfg-oQ(If)Ar{Z!)Z|Ov*zq^3CZt@BH_+a0mh{y z9@sEqHOfB^@9?!EoO?<^mCtsNW16~gC2QTp9BSIc!mpbt=r_u&zFn{QqFxj!lXxEl z+NyaV>D)ZfIvYpz#ZR%u*e#2Zr<&lJB2tYjMBBRp?$Y9sa6$Ris88Thv3k+$v(!{Y zpG33aLJI|Z0Y0T&*85PeH1s*5pwe9x`ey(`?4KGU|1-8fr=QMe=F~D}Ej?Q`U1se{ zZ)Pfp5fU%9Tj!LpIn1V)hP zN<9MAiyjy?T0H{&zz9ZnythF^wB0sy;7+qwp5oV~`7`gRa7gauROp;&>T<9VM4>Ze zpjTH62`jyP6+hteiOSm@`W)@<`SOOPM51V|m$0 zg%CRANWVcQ6pJ-m-k!i1)idm*Clq~?Vc#q|c#LhSI|5~XT+WS~pA_~!0v(tJKN>{$ z*zte!-H8*kst|5C+aNSuSyh>jR58BZfnortV`6bNU@m78-*wZ6soK@C*bD`m=NeMW zb_nkN&|$?luw%^=gYV)ue#I12$Hus2^`XU)LmGBAj%sE&AgIzug;P3MY*~dN+>!>e zsYk?Q+M_!8$$F`tZwvWss+whDiW-~);~M~i60p)OHf)~X2gwsCh6Ej(rGdTtF*&j#kHS$ z^!+>keL5CODW2cXtYL5<1d?O-ordEJo?5!x_qCmDy*kE$ef$x0J;a;a=}#K<;g>&kNtw`_;|<)2dpLnz#w z3p0d?$V26`m*4iQdwA;Q;9Rv$)%mQp51L;iuI9fOD}mMROk}WFAA#%~zIAssys1)_ z&+n9x^cJ0PX>FO`XrVE?C(DLguRIWcW~fK2wy?icckgGx|BK*p_gk8*#coel+i!~H zTPjhkO9Tr6ekz1RNr`Vu9XEmq1UIBFHac2V2#gJYI_|hu+K?5>)vOTQJo^^myp6Ix zM9ybHJU)x#81`5vxSr~+Mq9E&eze59nEZlE24R=@1~r!XiMt!qq5TdJ&K|VbvkcI( z99>7Ldjc~;L~AIN#u2+1Ls3~#Ew7|aaDiy@Ve!xxbO)_ofj5EV{sIjqyu=i=}>oa zjYBj*8(b$bf9Gdx!!Ck?%CAztn!0C21bxgAu&|Pe@E6K;{y?lv&NUtfLj<{2@M7jm zDFe?ZX^e`n$xS=98JWvn%ZO|9bq=A6fk~x#1&>|1>X}_cMZgeJo&wx<bXUs0n+a?@_K0nD`1Z=X~xeqk5xX{{_V{8Ap<>@Z?| zPdo5T;A|<0!fq6p%ZZ^!+Mzfnw0{E`XfFcJG2!s)%$EUK{1L~3$#Lv>S!sg#?hXo| zk6bXT98G)I;Ov-J)LlcUgiZ_!kDNuDRr$tdgjIHjH4zj(+y~r*1Z%d74qT}&SWJff zyij);MW8B?|DM4bLueDQu8ZX^*8=N$5kUK+d;~O#)0BIXeHqz3cM|UGoH&nSy-70Y zgq$hPK-kqgrH^5%gU>{}*K5^8%2u^KAUSlq@VA=R7-s?A7rSKt@ltp?5@5_B1I2=G z8qrR+qZ_T0kzWWkETxd)1e;EXU=f6h z=ew#Vr9vP2MTKtWUtm=Lb5^k=W!sR91U(vSTs;%S$G5iD7Zym`F}Q~qi-cEf!8Y7a z3Wo^nW;wXz*X}inwh*vEQBymbc>1}95Al*(T9s`k?FLLcxfLVH8|Cl*==UQE^o*P% zS2Zweag)(24q*h5g0*kJf`l}9$hswn!H2VIqT@)eMqT=Co2UlHOQ3>-#>b0>RGoO2 zFD1gh=$&W62fc!CqAqMMI%?D)xK@@09s(hvdDcH57H}#aEMAhrPt#Xm&b60d6&>Cn zeN@g7!Fc7xQ$-l3*4T{{?80~&>t?Fq@|L8pzqUQ$2&C!oh9VO@%O$Ani9M0@#YcwY zTGHAf+uKz+BNsaFSJ3v)BK&w!)bM~qQ9V|pR7I>`un-VE%&Q_x4HgF)EAwoRl{$6a zy2MgLbsuE##4K?M77+pKde~Ob3zf6`TmfkPM`V+jqx(uChP1Pz>7DG;3xu}AV}u)=7I&g+>y^pq;8G%o&XaN}VKvE0kLim-Ye(&*|)RX%}M zjPiQ7Y7^GVv2LjljaqAoTuWy{dH9@HrTJ9g2p4i%{6H)+=0US5KZf-K69Wljt|<6{ z1O4RLrF@F*l=9WlLoD4b46*FGUw@FOv#ia4mx6xni+?!%;V(n%taMF=MWfbCxPkoe zVE(x6MkC@pwa2q6X<>h9`b~i;&`GGv$XGX5sD0gWihoT@0EH4fje8}AD1y`i5ve|n z4?uN7M8O1AJ_oxP7aOc#9Lv)&3R5|f3l}7@Ifc5rU)QgfK{tp7WW?{%xAmSA&KM5^ zMfCD@qCqHM=-_}3Pa7EwoI(RR0&O~;h_WpFwA!o=241^wsHc?7(OR8+1JKqv!My2{ zq2fyC^J?#=E){iRU$lMBnOh_i!2Jdoqu`y1H^EPSWRG1j3)l#!VRCqv=G3Y^bwN05 z^!#XPwDb7$zz_9^H(?FTYUv|`y|+Q2;LL}aYH20*U6{l*btfEz{S6|R`HrQ@yQ;Jb zoZ3z}bvte1>K@YWsuiP6abctIbOa>vN?LnA&(&lF&o(l%ifCkCQ-9b6DPe~sO>a2S zAZD$VX9dymouX#14#Zg2ot`WgH)iQ-J{(;zw3%+vV4Lu2cz^*24aTs4A5ZB~B-+oHS3vA> zYP<5JN~2b0rKlT9jD`&O!m78XtWf!e+I+%#jF* zo6PWhsgEd=>A6NFr4!`O95`FctRXT+6bYc;hB4uu{0l_unfF7V_Ob6<8D0kQYZf0X zSelM=PY>UJ!)YRS2k6i5Qa44Db5mSu2jFY$z8tbX=jaKmGE}Q8H4D)&E^zyi7A6qx zQ>NVesaO#u;q9t>qv6K*!~E=p<<8;^u!_0I=#Q7#1;9%Qg zl)NYn%TjSQ1t3g;w%PF4invTkA<%@sL}A+58}zLe={?42sZgCDM8DQDHkGC>TsQc7 z>qyvznwbny0H*(Pashw1@98ykxPlt*MkasTsfMa#GdxCQ?RJpU?8V=GHW289;n4LX zP*?NNBp^}du6e)K$R$NCnz0`Cdmr%He){;M`JAIj9!v@Jj`6Zd&RxvF^wQPUH3(7$lue;4Q!u_R9%iG8S(a%fvI?F5BETcsVZVL}HX$r!E&n-p;pE3S7M#A?alK_uv zA=2XGMfHk@nP)wRC0%nuV?;j?L`E66CW&mXg0~LmjJBHFqHL374n^j*2z_Df^-qA2 z^k2Te&k`Es{0bFZWsd7e%f87*yX5c7YG2pHu!&1E0x3irvBQf+vXxE?3ti z1@%c!O zZmE=S?lGF82^8LqCFS1L0;|^vwk;4PaXY#@~gMZTg=+07(?C_QrqJbDN>$96}<5%hm1kUb`w#oY-k z2~fnZo-qnW7hAlTtbAyTHX5;v6Rk;wj3PVK&b8vE5C+t#H9Qk$f|Dlq=2LS>u*h#^ z1yL1dQ2h}|Df?U7Fk9o45ws}6>^ovr#Cu9TC7TB%0Y^vwtJ%;J<53=f3-RxW$&54q zmY3vfE^#_lL33Kfewn(iwJ)L=rX84|F{)DkJIIV0CjHDpmuQtQsn9n{oCqVrYR4mL zr4<~OefH1mBBT~}^iCV(wrg2<$JC{w3#zqijY*8-MUgZ}yz#a2NK)1HE`)C^cmEi| zlJhB*+W+tAzLsP!LGyH;H^sE!eWH02b$`eB2cS3Tls5%9be)U)~ z|KyQ;B5X3TBJ6%a{Yq7U4;tNN^OqCBxTtIpTx++!{P&Y9YX>&Pv%flwrzAFDoji>B z31?H5e55)_%t77$%E0IjA5FlmsR(%&7h;{3({m`V;OFl$lbqC<8XdI^{4+3OC}3P^ zC)T{QMze8ISvG=ib^Y?X%hv?in$?09bWQ&(@USVqJHV&n87;;SA7f9FYOUmrD-_ZU z^d(#N1AJY@T!iS}-aXy=k@F4nP3ApIF|jjUD0v&ejSnU=KVCs{-URV;d#9Qr9hpdF ztVJ_>OxcdlEa_3Qn3|fqM9Il6%i8BTFuDU-E*6-C{JOez2va03)I^?Gt+W)e`J7}D znBYH|ty#i1F1Rs`@Bz+Iq?G|YG5M;VoF)qpqXVWP>xG>lYUCnn6012iX1}+u(&EDJ z%dl;Su&hZ|rrlD;7D{|PHn~fLcb@1bs>+6I_6-)#Ydt3~J2~4W*bNy*?+r+#05%Ng z9j=R&l?EMozMa~EjS?@@E;*F)hL4q`8lKBAfSwC|M@qq36ly z6`LAn=EL2G?6z6hk|&z6JKo(@{Tgt+i5dSIS1SOa`{V|J=ecBeMFp_Gvchkp*C{}LjUa>XDwv-3YFm0K zo}5@5{%od|YqZmBth~`r4=vAKoubVd1x@=t#DHMUl0w?tfLiMzsD$2g`_^l1XxEB~ z=T2%{H|iN(m#-oeJBn9#U^K780*qAHr!xV`^xsSg6;So*l^W@vSyt~C9xh%pu`2_(5LXbAznw3SjHJuL467L6OdUP~2(aqRmR ze|vos)N?|!BATH^PK)H9&6F((88l(3uF99_bVk}G7q>@oE_*+^x7egG6c{I2UoZMf zG6UT!W@)&^qzl<~L&rEYl}#ll7Miz#1Ry>ml4DX(>GmnDvU~+}e4dDBpu1qrt2Zf{ zfca(rb%Z^^u7U2&{U=hqU38LTvIKoa*0H`^y#2w?{h7sGrKE3ti ztS@8jbXm4VU@b-V>O0%vmM_kZ4A_Mb1`<@Cv=h#hFX`B<%2tza;18VpiFDKgSdQM8 z?;SLq9SVvoY5cSz@32l(6}M!LRL0ubG$UT&%cwr)KLv$rg_qUKx3IZR>!hMufYd=+El z44A$pE04iUUiKCD48U9c6r>hviCtNQk%h1=??c)n(D>mbL0QtFGgUjFI}4(nBny)O zq~L2b{Q_QW`Ih=#`oMxg=aOIMduPruPxZ?-jvwj+iZdwXB^yAN7fb4Uw4%$GY2sP4H;)Koes&k7-0x;bF>)pnTcdDN4K<*@SpWD@s zwLf7w)G9o##k6xpsnjbR#As6$G9V|i+Is|2yQ2&0llO#`XRFl}4uu#RUwv{c+W@{l z-FO6YE#Him`KY-_tX))yID{YhFKS{h|GaX0xMB6?&SL!IY~T5Gw%4L?u~KIFy}gK^Cq{%juK(fk6^Dq(wx_NZ*}AZ)eIukAV3i zwEC!>OSojXB;@2$urrO|`Fx{f<24X84pS(x*P>OTT)1ksR_3d}IUK6;$;I2PJy?vz zlS4?2zS*~QeF0P7;phCk8aVe#H;o%i_=Zv*)BAF~z3h6)l~8W;UgO2bi*rY9E78pi@hLvk}MrSZT0#tnb6?F(?xI12%K2f|3%uMl)yhT6To?hK|AeSCO=&iL-`m(xxtL)XvM=O;^ z0->=7`-rH0&{<3Ov18p1qa|==$Gi6+uCwkgJ7DLHZI|O3?-UT9qQIWULPOi;gPs-j zkRV4s@!|csq#HsZQ<;9(agS}JzJYp&@+Lu?HFG-08ICK`qu)UHRCAshxV!ne%=T^5 z^5!HIFaNVbG|>5vxO-vL^Uvu(P#M-)luEtxF*`#Qc}aq+LAFfWUUK<8ecSg~a4mr{ zx~OqQKl<~K`HFkREiGKdipDPD!Sza|gW;WH-hl1LwJ%DYn%AYgY?9qq4NSf7*|HRq zp%b?zismgH78pyQ8VeNrK2{+6?w&GSE9nb(4Ldid)x4A)FKI)jt@LX6Bgab>q*tV~ zTWV5@;XtZ_$cLBnd?yq#L(M3QDcU!RWBCFr{$utVNU}G!M8ON-kg3Sp?SyyX7K{c6 znnW2sM_Jnt_OP*jVJ_fQdEL})(apKp1MLB|&=(Rn*1B@CzBKUvZ@}AME5?85ioq9I zGzNRUnXzaj>3~~#25{aZM*|`CDfxfsSBIt8H_U2r4KFI{`xsvtxV)+ObJx#{oWG;Y z&$P!Byp`8`izmTlzf^mAd3r6I?rgLO_7-QYI7HM#oG|%&qt^>7>YSt;A>>GujIuew z!khqkxBxaNNe)XYz?FBzO=ElkLQW#MD*ycN&KG|k=(30w8RL}wwEZe8;CVOQ(-Ts# zBcU1)x*-7^)!@iOyF#Ar1_DES%H5s~sIo@Xx6GC-fHMH+7Kp9jzkhg}9G znj+U!+kQG(a{gU*zEj$&*+&EAxaP9ZEbVs3!llo=ine#T4B@`X%xp5hU;1!zhkv}z zFs-2a)=UC_?1|YamDnAqmP7tfH}r0~g_dH_psu&~8f z*dKC-Dwn%HjZW4a<9|PXatcN-3yt+QIe?07I*UI!_tBD2P32TOBGl$3i#$2s^8DDS zVom-CTpZM9n=%mbxiqB0iGk6!95viX9A%FR-71yX1D(sOxCfN>yse}GyG^&@!i_k~ zM4rJ4g`>iSeZ7`vPySg^90=s3g%c7!@R_fXWof`cPB$fFn&Y0nTiSJ+w%II9g@XjNT!sO_NByTY&w_lt- zZ$p=Mq*0vP@UGq>hvwWL34 z-+|0ZmiZ==uN`+p26J9`cPbw?o#z>j({#*Rn9==uYU6=s+;Y`j;BY8qv(YzGf?Ow$ z=Dw{vZN7}~^K{;693MSGe?I={RQB@g(0NDqb)XqrtQDUGd#9UxbE8AuDvo0*_rLzL z>I0zu06>djv@i6@%e62fnJe>$G*%+fm44kN=sivL{D^m;w~2}S@m33B>0l@90@B!; z&%yPDxb?~uR;Xba*(8-yutMv{u0;gdtICa62U%LWO1Qv$y~Wzov~*D?;8=ahSS>Do zN&d^CT35#VKc95se=*NfoCc(aio9(n@{X~Q>8J92r<*)s(V=z~$GL?Dpfj+XuD!OX z<5ABAVb3S=^n~ES0ItJ?(avrFc`i=(V_r9m40UeQh(7x(s9(a%ysTa)m14fNk-iGr zE8uJ1u@Md>Ue60y*r3bq!B+b2j-8U&ky0%y3L0PYI4+qAy#WGpWtv;^N&)}ha%Dh? zk^b=Oy6K|ch)WGb!S=9Y%VMkmhp(1u^Grgd`sPItFF9K}cdrTFA~8mg{|w2<6hc*x zvesf>MN3?~WykDIRtp`p@SDco=#Tm(P`r7NwPTuzc=ttwt`p{K$$AHnbK~0YTHM)k z(b>#QK?79JG9L?u{_MxU_PqbtJETDrc}K)UiDy1Z@WU39SaSWN#;C*;pA{A<7a58dy~D$Brk+eyC<`PmCAm{i_HWRZ%B)6Uu)clGt&*B zBE$Y~kFkjAnY=c)`YFp=YkABMc|%3Z;~gtejkWtgjr5cMzqx4;NTSp?Q*`9P;HBpB zijiKI*AFj!ROj5{FFjDUx;MbkG>JuNafNt<8Qc(YoYqM1nj2NsB@O?GG|~CdA?mGT z8noyY<_&qVfG(wsK6H7cWAC^ERTbJb_YsUWjzyTJ;{ir#A;5(!1d^avFnsU+nqmLp zv4~SM`$6r~HeEnSoHtTl($27trzl$72S_Fp2CE7%-S27ZG&La{jVXVnxV;M+?ltn8 zcUYwJ=pBpO`jYxg*&Gfjvbb`q&Z7(68H)yj?EvDB590rIwti^qmmU+91$g>_8tJK} zdGQA+aeYf(D!&n0RRUdU#{gBkm!9;@(>$<9**vwvrSap=*XvrUn$6Vd zB8RfXSNYlVeuL(n(?Yn8IQx!*I=3`C!HyN0f^J3@1bJ+|2xB5jU7Ov}ev6LDasN z=h_k>TDg7}sA-hRe3Wo4?Zvzk-hl{)D*Ekg7c2T@;7dk-Y1d!<)7<~^&neDQodT}M z^2STT=H-d@#craOw5EL@X_T(aLCd-7CsLXZ_eXN1y-9x6$E`P!-P=n?qR^aR7R0CT z!sqhumR$W8_c?ADE|!=cY-FEfeKTi*ahW=7LmLCIumLDpyl6VYF3N2!^H9S<_(@4} zqm&F{*r946P2%7!($CIIST&-HfPO`YzN3}wtbh5qIZe{;*!f?a_rLX(x#8k*LeBbj z`1*8&+w#~>o5J{`aduNuVvHrQ)Cxc}7_$w8KYau3X6#-YJUy<&T9`Sc!run3AuDS= zHs>L^>Uo4GbIQ`0r&Ae0^2a!0wfehnyL!kk7B`lcW%hP}BVc_*l?%c&Y z9QwD*mDt+KC%|dp_si6)`q-5K(L4ZmI*zxleoN#P)vg79%sebBpxo0soJ8pL`b{q0@hqJ`5FerZhaT)r%#A+*He_hEPC&RZyq5_0|2;d43-G zwDQ7ftmhjO0IS&?6olqDzVIqjvOQuIuldpBhT^rM2})T(cP-RAH;by9S-$|ov#d0$ zH^ND{6%6v)YtXq|j^57cdgX}{^Vb<9n{Xj7!}K^`2LfQR`(wXJmx||C<``K^64k2; zM&N@FloT8Xcw(ESidH9f6{)fjZEQQB(|J1&Wf!v6Yoa_+D5_$&$J)h!nE{@cDS_V8 zc&;Fx7sAIE5>DmgXV}7e`La@?Co4%D0*E?N!%JL;mY-F!x`;xLu4)M;$6~PpXZ6rj(h~eXh)7I`LV}9oQi<9(QlN1j`=orWd-llX*Aom0(k zf}OkT^TpZh04k({3&qppJBUOLgV?2*PktTm&i^srm{w(C6zKg~MXui2NL|h3N~Xe= zt^@tTb?=GVJY!8kLA*zbD*vcka`;%U1(e4XGgh+RUR0PSRDHqtSW4xQKBEKvPhz^w z|0B~v$@Lp3yK$sozuXRPd%ASTs_9vawA?d&MgSq#M`Bp{qu#?VRM0VDroXRdpSp9g zIxl=&u^638b}@ifa~4A3b8V8o29oVKZUKgRN9w83BG$B=zRY256hTx8OEXAL|dkGo^OqX~IS+!y7Hmju?SkJUzhmkt`Ux6P{)DdaDTYjvy!08WPYP>7)Z{G;O& zQ1l>BNWB)26OyYuV)g+&W+{hcrD4BzdH^OEx`*-Y!~jaa>(Bt-!+h7#V7s8Ok-mCQ zE<{NmtXP<66Ac#t>e%qAKyj8%3?Q~du>-B_Gq$(7b}hfomtvnhP$_m_3wmo$hQSqS zt{x0a*@)1ma0fcCVsikq$QVMS*@74PVNTz${#5-ea(fk1$zPQzC`6}JmpYD)%se^u zi{&4PqVYktu4ADTAo^SMDCIF(L&V8bGIgN6GBKvQ%I>l#lod_5vOYcqI)*M zlI+CqTZ|Ok%B>4Ryp0(F+nh1XNfai0W~~b>-rXHzoWbAGU#+{-RKWK3pU?dpAq&^D z6I75LJWNs!5)7~}Z%-%d^+B?lOGv#ON<45RB}Oh29F)$#>T!Hq;EAT`d^gb;(d}X& zq$|KkB3LY90WKC<9Kau>WEt)F=q#u2o{_;`No%yITWr!Ure_k*_XEFqFoX7_Z|jw6 zZw=X7QF~IyIb#?5v?tEIsf`_-|Lf!b@irN@Q(A~1Uk*xBFLCZlL6?(^xdtXlp$b*P z5g)FaB?1Bhyg(LljVgKnB6jF$k4w!j&FmEojtk(f z#tk3la#=ke`vHYZioop1;|s~@q=7@j?>v^X`1)N)IsaYo&FL!(@#Rr3eWDtcy~Bt@ z&~T&Ho-vN)j4+-Oi)5o-vSigGQ(QjKfzE# zgz2V@;A#jK^}DLjC_iC^oHp~#(2KHeszPHODYkxjFNSHxMX^nkYLcEj)VYpNQn{D% zK0G5(yhmQDyWsORk%oi<@he3P(lZlZe&X_cs#$tN$ zs@!O2NmaHK`A94jCX1^AsBVBA`H=ZSy`SP%dxNJ;bw_)K0BWVQ*4t{>vRP+l)MKLY zAdU~l)MRmgw>rbU*OHr38`~S;(wrb$@rn?yo7PlV?Jl)h845LQtD_vMUe5oxdf+B; zp^%7ja?(Rw-`=#xyq+_`bDaeA8UfhjK*8n{5JqKk`GNC+MULLW9fW#=sQu!S4NJLrrI7&lS!^8_(lAM2Uv?2t(8gVF3kIF)f|UQx{aYqCE|l0J_!Z)5#d~aXOJqC+ zaLrT)i>4Qw)hhinB7T>9O4?i~o65MG^tyMxKaW}XPES{W!v%IKR9G9(!J9${zmLz4=&7f)9)$AB482v!TUMbxv%4Np33q$c zb|tnK<%tZ)ZBHvp_+O60rqPpTTv@C06S|HHJToFs%WbHK(6rf@j`BiJ4Z_8S9>=6OdM zCstcQ!b~*nu{YBP)mqqDO-pue*>OCq%p#V!!fO}>nm9diK_lD9ri&l`cCh>NRge6W zf?{KFMyHR&kniVe?q><&&9fBMf4GyMG%{nP0AAQ}D$wGRHpYx(`H*vH1L$<$jfJugs#m#L^O zter=R+(>)*K%>wdZO2c?lEcgD`546Cw*z!l!lXqX5=Bn&z|$|1%o#r`2gB{6CW=u_ zLvys!n0THlGjS@(b>d#cs;F;%&8Xx9pz3z6;S)S7(i5Kcn7)iLR ze{59`MK;MsVYM|~8kI&RRa6%u%VE_XpjSg-z4ufNzUXaWl3!WaWO8zRMuaPHs8yc% z29j|=4~Pg%@o+mNawonjJi=dmhU^~G8*J@-Etn~&!SiyFs=nJ4BMsm)zYA}vnc?!; z^p4cqr3bqn$V*x=J5C`QGdz`{kFtMqV=X7)1|6C7z5QT=&gjC@^89Q`)tha4(>br` z8+BK3EnvajzqboWL(-Kc6LY_Bw-}x#iH8-t3uA-YWAC)^X`5F~z0k!@A9}H8D(+h@ z>P=5u9LlETbfLe2{H`z>m4}5Jy;vwF4)E7vN;V#_%eP2Y`sK$`tJ>+SS^&Vbbjs~C z5Cx%m_hnyyR*b24%8yHC~TDd5`qOdjYjZ@&E4(C6#Z+4ZCr0ErX~r+N)^)KnjZS@^d$BxL>r64P6J4> zz#;C(#x1*ErPC=WIrI-R3&D#ApV|~{DzL8-zhYKnZ#EV1HduN5idKGfE4lpX!aJXH zT@eCr>2IVf&j0n{e@}xjS6=MNw!pfiiy;vg+e+m|0bRknm44NDxn0K{^_NF$fvvhj z?R^reuuMsU5c#QPHz04BrB4nY<8{bn} zas;^*8t0a3$DSa@WaH-wk~6cpt1fD`>Ba!UR*MC*OX}8`t;yQxO&!&qPO#_w0KjLz z2X*)9s!~+p>)qTZg}H#wE_cxQCQV3p>~cf#oPi`i->BrZPp1b;uS3SrUQZl{LF0Xe z`aVv!E5|H?8=l=HHKEvX-B22&XnKITk07D-53~LUr{ODI@hQ)WZCltkm!~qZ(<*Ww z9uk6<;tUW#7I8z1XII}S6cXZC{Z%Ap!reHLyEpF27ay^vp-0Pziaf?`Pc1UyH6UFx zD%m4&kl+s7oOu$-um`_}sctPsFNH zY)FpgZ&h(y$U~<8F<<_81OVY1_|I?zt#pZhdWjd7E7} zqLyTA^F?`k#c|Pq>D9AtV95XL#cku*>ab) zis5(G!h@Db6$AmzI}NUbiyL`yydNvhTxk*V*G4O&CkBkKU^Ee8ukf4k#4i&L(n9g_ zt!JVJ+ZdpTUjH!hLMqFmCkWPO)}De@SB>nZi>14TP{o?Q~sQjn3QeJTR(x zePbgg7?jB(j+k+xHVW!o5l&>dN(=wL3~AKG_>3eNoNcK`Kk z_qcSAD24PEDQ}=$=5p0bQfyOqt#|~aEi9;_0~y1e(Po?SPM(G-L1~F=?;m2t#sUHZ z+%vGND@HJu9r4G($rSW1KW|-zLPcMneqf)iLD&QNdKvrAY{8vh_Y zcGXW|gdzg3K!4@VFr!&ey}p#$7}K^XR^t|p!x9n*<9auzY-4AANlmP!*zB3x${`qt z((6s@2z{fVC&(i5f{~!27CAW4rEQ%mPjy}jWcb%5k7BvJ6A%7eCfA{)b+(y1;5+GnF*$#|sH!OhJzE)ci^hfpdi;dgU zDc;skp>A=}upKKz?}&ANoregHo8Ip$+QzG_Qy|$fF#7Z#ylJ4RyjxklSp)TmBT-8S ztaP-iRcVBg(BqF$TDAeM9;IszSbric_!Xw`cz5mR{P5e&98S!7a-i!TnJ-iS}${>M)2oEjZ!UpNB&fG>k8payC#dx;iI$&weq8WU-Tu3GuJv zcAaV0_-!*FI~OMj@ggP(TUtuMkqV6ss~FdjKCJqdOIOJ2j$?pKP}}~-=1+gK$-lmS z4`dbS{CF{^(lw{jvXiKy5H_w^EuR1f+L&ginJ;8THyuF z1AHLL$9y-HB;fpvFvi5{GWhLQ%Hcxf0K%o#lLnlbY?QN-{~;ex%Ttanj!v#BNFg`2 zzyazr3vD+$jtngS4|lqt^SO9d2%0}~ z&k6Wa^uY}q^x1l-@@4C^nw{b`&aP4OfssWI!HD(7ukbu?n$=LBT9;|((qn0O$zs9< z3egpiZ-BymD|n_WIQwjS!h)*r%ae7-n3ns;rh0@!OgK%a2-Cp@1qV?(lg>Vkx}1{!2%e z(d8CpMcXuhpj&LL-%M&VpWet|WXx*NxKWjTy^m;FPAjHKSLdB@MacoTk8skxp%hJP?oXo>|EpqE2dI?S;7^%%&I*# zN@K5@o$*aI01LsL=OlCj{Nxc!;rpc5UHw*%D+QPiQx93sYjb7q=c1q_NhSI5g!1#( zvUnE7CStXh;Y5H)Qn8ugF=d7I%;_)S>(2F}6L1fX!bVAHAxw9!+X4;odKDLPqLm>1 z6JV+DQKbbs)!>LF##3ro%t5WU{Ftb#z=Lyt+}#2Cng8ZECM?;?KU&x`W2_`nM?8xvMAH%rS<9D6xL@%*_+>4jAcL zxO_3%CqRPOx}9qA(8@Kt*_zj{XutmHgtd*{>_&BFFE=~q{JObL@{&gW*RnqI*b223 z)KjQ1*_6*Ze9Ewbol=LW(5%lH<4BXHV;_Wn zdR()ODoeq=E@j`sHWCE~C@S`c=X5JIW)Q`b>wsec90Wp~({rOr!4RPYam!JZlA7H> zZnb)xX>lp01lDE1yuOqoyNj$bh-RNv{UwmSzGiE-R%chy1`x;=+!(afVtZeI=wnwB zQ2Y47$_-$~fsPFzg>~`IdTQxtBD~#D9eoXTr4%JI>JBEoG1^s3Vy|#j`(1Il8pLiS zapW5)l5tIstg<;p2AEC7+86-WtI16m69gPHR{DWQ{2oWY6Vv_MYhvTv{SWIIj6rYZ zr6theNPi!WT$DlrO#55_{R|RgIqdpwWC%Xor zV32v^6uuvj0}=H=;FSBG6*Vo0^hP*&4E1{p#A@K{!1`61Uh7U|e6Bn%wHeu;X?dvr zqE_dt(UX(rUrDrC>cAFcn~~9c&f~6Wh3xmX%3O#Qrv^3trt=7wfUBP*tVw3>^37*k~qigC@HZ(g&_|%OUv26jt??{TKH> z_K%*3t3wDIOQV|DoL8Grp67LH53vFa0nT@HnNGT|fsfqSRKOaIkk1~)s?0`REsw)j#Kb*O8 z@LE#~(7+(5d4_+20zcA>_VRS{)LZVX)>D`Tc*EvR0DX7C_LzPHhI*hfEodQisHYED zbCCE7=FV*?N%>YDouIIBHX-=<%y2!X{-73d5Ws9TT$?kGLm%NZ|rbO=`Ug0*w~| zYIc7jQ_a3b2t>m|OJamp9&xti4O_S)?M?h8?TZadlNCi3X^Q*FE++ME6fI4N)pG!2 z`=6t-3leYh$+KEWWcKax(os$VByf}B-rNV*QqQK zZ49-b%Q{liY969?yCKdr|MlxMZQoVyw5)zFA7sw?`FsMcDS(j>1Ib#IzGd+!_cj9GDTI0 z(8iDlW7#V$Jk0kh%YBdd^3_|w2kU%M6W)Ol0JmAn3^hxC0^B(}k$@TH65FQy3mb@u zH&WYth&wBenLZxsVrhKO?AxDx1zoo*m(NAFS~rom9(3}GaI7XF(mOuym}M?m{;@{w zmBY;%HF1P6meuNUNddU&?{X48G3LLy0;G#oyz7g-+HIUVlP6I|J`r?i42Op?I@R0O zbf*Y)D7Vu==Ub(LcL?K-*p?V4wWb%2Uue(GY8pL*-X|m^unCm7#5)lHis}j8rohXx z=L6ceF`J2*Nu%0=&Xp3@cFOVAai$~MLC}$u;a6F(^x(x8~~|iNTx}UG+u@ansm6hc6$~T<7;b4r=k5M?e>4o zHnQ))wG~@(b}Va7A11i^B`NqcW=~@kvBKHrr>hBYk2&4Tnu|{3_>#7snC2S)HHo+@ z^?9p_^Sh1`4mPdpz3JWA^gf`r-8hA1fWr}gvTlz=&72ZcVC=QM+LLYM50$f7M?Iq6 z&o*Lgnr%^R)|knH@Jo3-Wyypf6-S>1ua>Y85%G;2(xRsLKpzLpx&go}jk6qnq5{jD`g5%-6IdAY`;y6!m)@xksHDxM7gm!w7slbPzt+v^ zGK~9O1qy{zSUrEmgfJ{6;tI|ZoTE$(EFnTDy_h>4HcUI#w~!3u;4nrH=^3o1o`QIt zs=%=|4uuD*u7FylKZP3rzRDw?#R+0nM7jjy>X(hUhi-v`0Rn=k@$04wi@=Rr7^t=> zTvtLZfH3o;GIvlR9TEA%e+pG1Cyioncrp--*->o2538_JS36X5fcnQkaKkS~{iAzfU2GYDUmesW)a!d6%%=7`}V zzr!Pe@Jyqsq{)@NeH|vl)PP&fqZ^wsoXHyNUU9WEbKD9l!ILv0p>3VYF5U;tt%iTy z@V~hY4MU~fhfutO>9LOFi}%^Pw-5Ff*EoxWF^W4R0z8dxd}o+rxp{t#;PNY_ZNNj$ z;bP<~OdO2xy3}`-pnkNaypkqE<1kTz4k9~z-^k|GB5t<%E7}zSbEwN=h^DqVc6>{d z*XSVayeF`LomE|KL3NhcbvoL%!&sg}-IOE{^{fqZx=PiA<0|z@;axi+sYDHi?zfg- z)xBT<8$fSSpkYzR^j*?2!~r8McULdHAe!r@Lk@9!fi*~itTb9p~AThmJ{Ln z%G>LiNg9BiP}t@-z5`vjLZ-x!;RkX!CwtYa^^wd*pesb%>bA$FIpOF1(xrGT=tzWa zei}zQK^JL=my@jBH>+);0QBgs!!L2{4`K>(xY%fA)yt=1#)Dco+ON%b*9#ACOqOLm~(+rSC4*%@3;ENbK2OlCpeJPNa# zIw~LRErVk%tuVA-UC2iooxbA( z6Lu9wM@9e+{B(-Vt(l19p@O}Mvs$P;I1j}2e2h`_oQe7aaA^&;Te9?c#tF^Pw1&Eu zU>{o#AYb|yC$JAT0AJznuJfvK^3&cOy|=V)+TZPZ@l}~r)SeRG6ZU1(MIZdQkP5_S zau!V0``+fdOWwR(WSXU0ixur?Dd`@Q%*Q>jt;o^`*mdiT4W$nq78{+*G~~h@FG=@t zeOTiPI@H*HT};kJ&ufuX3ZYy`K(IPeHuwn$q!Dzm;C7QbbG0g~stny%ztTTXL7`{i+7k`dIZ# zt|^|#2L>@bR-N7Hlm(qNcK_g#>f~?{?NkV57XtTX3&o&F&^rfv)8j-Ruy&Nk^>yc+ zqs0f=H?FgmHIX>hM^fa^Vcm15@m)$^H(+V{!7Fxtt~`eV8wamlfA;zcOqbtx+#n?5 z166x8^PpAJkZdLAo2e;LgGSEcjA)g6SWTLc(Rfb&b^UVY_ddD4U-|-@oUr10yHKk< zuDjLHqC^KWaN@ykZRit}gp#RYXK%SEXFv;q2u+dF)6J8W z*h{A?BroR2tUK#$IUee!jbb`h>X^JKm(N5_Sp~7sl$X+b%VOAkV4s(&#|olAHmbQ&MfpLqtJl@RJ9y{^+Xqn6^;8bP!7mNvi7Tr@AJ_0{+rm?uamF=cLPaT<|jx>ny zNYk*m{_-P0|I<$13dDN;QIpmt1YrMUi+WHJ+gEo}y=wQUfkG>RFX)1GJtRYHeF*Sv zzcE9J84$?FK*D+Fd?z(}LpVlm-gKt$i!wThV?wPGDcD5=>uR)x!%HdJQ_tW@qxgbL zZwYHyVw6d=>JaCCby!2eVjW3rLNIjX@(tdeEcb`0Kf2x{sx|KX;@p|}M(Pi9tNXMP7TQXeUH@2X zab;-Q1d^&2ei?M;PD@~R@lP+DlQX%{T`S)~gawG(jw_>c1Y|-ihMGCj_+oF;*qA^i zEe(<+!x*DZ$3OzMAj|IseC%d7R(b3Kgb0(xgdY2c7Pf&v=c*6V;m{PrKBYUorlK@M zJb!==TU-#+OM((atS3dYO^qI6ahu~~%Vf~p>X(ItHE}>CzPF4-b~cc4WWnbY(3uln z6kTWY+X-QJ3I>>QUzBE=$8ZU(kbs}a%K_q^(*dQ021*|p8gp_}U%=nWfBkAW6fC%_>$TJGvKGu|innB@ZS#Pcm-lwZ-YlovX{D zE$`H}Tra$ghRb7mgyU)%9Sch_0tXY4p-&ruS#@za&zxv(eRsex`Hq1C*ClDaml3lq zJ3_Zgbb$TiXg1`swzTIow=}Rt?G$MwacND3NETzXG>@sHJ)b9mM+%Yf>_|N<*i?Ad znG@I98RDL4tec*4*Y_AsB5(;X)KwiI!7BNJCDU6i7t!7j*X4~mTb~-TowS zC`p9AQM`SsZBi{F5t5jg|0U|xswLK(t8szmfpXQd29#cc)^>$3iU67i2=_KVdvR@9;klOx>M(}hVdtI5EHeT(md7oy;t|)Y@<{qSpMv?hPwvoZ3HRT6o3bCnlJeGV9Uaudj@=Jy`o>g7=`F(G!k6ByS@wJ+ocC9#Do$zvz0mFWnk! z)q`g%;F7R%>9lAml-uE$&?{vR{RUD@I3NSzN7vw7o6you-y)Set%_zySmz8+o>s5F zj{ANX_#j&Ocn|e)0<^Tx`@vlviofY=Qye~67OrZiWg`<_R2fQlbwXUTFC`jc68(y% zFfRdw6Bh@tMu!Rj(M^F-S8H;JW%g^IH=QA;%!7}@Q4KD-HH!zydK;#39!HeV>n{T@ z=LJl2Lb_sZxA};S7=J8SP>tL`4^$JEU*gk4W91bel#~$z(0a=w=q>SeePi7k4j^!f zTa6M0C{2{mo6kISKpi7kQPXdrhM}RM3G=G}-)$G(!Z`CA=(GHL=d<(fw;ew3UeCm{ z7f)d|jwbrU`3>a~90wG;NX#i*&Mn7tz}Z36$tSc@Np4Sxc}q0#Qm=KnHkCH_3`LXf zN*gC_{`}@2cbz}~hpb*wITrW$1yL}vJr&BOlp%TJjpb{efpV|rvWP4o+a#gmI|?x^ z?x?}t3=?gUlqrac8mx|v)7QgqUpbavG8MfTI^@!`ap7Z8P}Bl*v_-QCd(*PZn8j_q z*V_kS@uGEXPO)-}(7=}j3o6fikm4HkqS!yQ8PLQg@I1Sq%fp0|1| zz!>XXXVv)Zfra-fpq#cjNL5#%cujcqzEm?XAD$Afc zVy(Z|al3VbxhfCoXB1#FnA7b-+o#aLb+X5@$nypB5gGFp)e9G2L*A-w9Ia5Q!b-#h z3ji6+LNc=#nJM`wwxuS5CdtK%EQ^kji$IL`Tlc6Yst(??vh2;|DeTTXsjf@oJp{Hu znRL+k-&~tAX*@Ox-e2}Am;W%K%3VaQCS6Yw9#GqfpTZWl09qqe7{^pMRjz)8-fS2t zkkXs2=9H4&0i>yc@JSQ6_C_2%ky9fto#)}YkeDbn=<$W=j9Xqji4J)E00A|nQ8jx1 zw zcfw$0l|DrE9c14wE-_e^&3sM+FO83)Nyi6@??0TY<4R0W_UR>6C?sAQ2};77Uz^vm z*+RLy)Mbn&gfhn7|A&#xrzisJjp6b`>|cp4R$&t+1JNcyA*|y!5SnbW(PF8$*Cz|h z$6qPVinMNlURg1TMiFHW+#Wu8ZTZ%8MI69dzI7vljV_dK7!;~%Qo^GB+rELCT<-(F z;#!=S8{z6~gixEq+Y_tm*SJ1SD0{aj*bA4IA9r4fs4sMGkpzzOd>~8e>+F~XviO>p#?lG)4>%^8hF+kuahk~{&r7ysJZjv&yU=UtP_A7$w|cI-824t5TD zE<5aO^ICe|Su`e!0i{~3(F=1F1UE~m3vLTI(GmyDP7m#v&kyiQ@S4J!0 zyVSlSby5xt8(HFwaJAc4_#Eh1k0z>HvxwlNoy!54WVy>zYYOrL^5$Jy3oh45w-lLh)D3L!4<`J+Oi%jQM($WQX1FeVKZ2N6X*CWe9o&@QWoo zCRV7jPoMl`F#L&|WFqV8qs<-0v!z!2#CDYGUmpp7uVv})c_{6T02t4?2K`Ic z3o}yO_xFQo+XY#nahj{(qC))=RQ^0=37=VilngIB)_a8uF#Jg)NuIUgp%s+QUMG`1 zU<)_HS64S)coj<$k8N_J&>+fV;1lKgv-R^z6|eA+FuMrnS_?PB(H~k&NwI@nykv-A zQNy;U_OM+)2?hYSM_PC_r0W`;dx@S<{iwh~Gm^1|k>I+)3DwRTz=NeK8}h;D%TfB> zSHCY({E{Hg-sZ?k@_xS40-Iki5=@BX@++)U^8ysQ^XcHj3$}e--l06bimuTd&L!;r z!Z?&ER$APMXR1}6!>*C@WyH8Kohd)Ab|A5u;pcaJA*X(O@HUmZetm=yrO+x6X>Cj? zCdxRH5ODyn)6p81>&N-pG&N0g^4Tc^Hn@ifISgLsy1!1N46leo<@-{1+92)WBY=X7 zUkb3{gH#j~H{s9bD8CF4Z%*McG=Z3W!*-dm@^e^ zZ`J0co-6TW6qa8}f+#`}ucxsEM;!wtEW1B`eR4PWwvrYFmhHE0kDW4H;O*#{LT~Bo zI*yvxizHHDqaL$VH7}Dz9SYN{#o-LBcOJ3jrh}!`9MP)BE(U3CT<2_7lEu)X=Ul%g zuDjc5bor2?#p2t}*PMaZ1@Z%0SL?Wd3_;|TpU8fGhU?Jqhlkoa1b}8kUN|rXzBE75 zNnn%Ae_J+IgKZzrhVjhtH(r)1SP=`o>3BCt*$#**$d8wT@luiIDbwrAx!DuO0Bewx;>Ql?uJ#n;D%|!a1VtiNT&n>wL!eBmPIesoe1xc3D0ikoC*a#M!ZL^rv@5t^H@AK;Oj$e<|B>uHLFRLWQ zG>gI0BUcnE3wZW#({bFPIyLy+kU1);KRP|(5$aJggK7AvS6ThCSa(F5q&@w7SDjf_ zgd{!$W&7@7ybF>hlu9t4ygF_~7(Uz+qK(y-Nh0m}I_MzK$q;&Bu4bB%mcr zJgTLy%BrmgG8dSQd|#F{Qa8U!Qntft(M5*DYhLvrKcyh)h1s`FB8Vg;>sql?_VpeJrDlsZ}>sTRM za3XxI0FQ2J5-4g&3P(Z=F&%_}0^t*0hsfa|%URO&$<&R0(K(uI;`I?$eIk24r%T$h zJnF~`wG1yJTjeAsGs2UuoVVzQ$;tfoNw@<^r*+v2*QxK3MfX9mET{r6bT_@%3IT%F z8o@d<+|Sp(stopQ`l_{ehoLIopbFVGtrtFcT@8TBCw8%x|F$*FI-5$7W5HI_292t z^DJxNc z;-y+r^+;GHpu}7w-sYHFr{E<&Vay_*YB@Whu4l?D*3( zEEewfF&ZhSgMy~F5OR>l*|BoNPP*mG7R?vuv9KUu3zfr$k#)R=*X6)(ms4uZpG^y8 zKqYnGIi*2dj73-V$X8P-{hD(q40|%?>HK%eFY)=fmWdrexy?=UdW%!=uw&;_!Pntk zFsRvTi9H5xTXg&wtP0>8U2{bcfD>v4aP|zyrw{T)oqW-MtZjpW@Kql#Ic1BV#kdY1 zsjlYGw4l(|Ci}Wg{iCgFeOsGytVH+#Xnac5C-XaZ=-C$WM`ViDc80z_0=;DH7}S1d ztq||WScf0?{xYmL{^7Dyp%XSHq;uXyK-N%$%1Z!wI)WfTZ9Gs1U7MV_T@v$hZN;8c zRjlUvb}U)_#~T=MJ+7yeky0}=N6X%v0Pi@OHYEQ6jDo+G(M4K$Gsdg2kC10A< zBndoELvSHsTQ4pEubIb5Yh9}cdtO`)_56Ki{A#;XRQBpw;g2104O4@;J&}#J(b>+i z;;6^dsH^9V?sCsLOEtDeCJI+m7Yww9(ayEb55Wo+I=3(|fVQy9OyhqIG}w?}H{+xo zL)>tD`OYaO$N7$0I`acOB43`ZkdzOeD_jE`3?{17uQmM<=PW$VHGN`pSpv+@m^Gsm z!`9RUQ3@oI1i+a*JBl+ZfG~jbnOzF)aZkYWzBi+7C3n3sq=zGiI`3b5qmk{}Ur;bY zatdp;O`m6hOxc`7H`OqmD{WD~PPmS#cM*baXJ{hEYFhiUnWkvy{ekiV$UsU+QPmNg zECelT`V8=yOQEbDYamkt*--MC@EZyQ<0NG`oV;lMi5cjqcgmZlVo7N zGJ)sD-z=rmEFnOYDVU!I0Q%RWN)C3rez1*%Qme#SPznjrvBpRzXv=VJbMaN2lwuuD zblM~W%`&2b=!5s(zXo;zMF&JbtdM%dDbF*OuXzR_@=puttEs~3i)YnsFOVa{cs(Vq13gTMHB(wjm@}o0T@1}4$(J> z8Kv)ItKLQFQ3^|RSxArLpMV(`wz81?uDj&SFOETU8KsM{(DF&fBb}QJ(({YWx*a91 zsEqO}*7=X(?--AHHxwJ7JLW9aJsb^zd{2CQkqu9m`5)<|Flcgw=Snkv}8i@hTpb>(imE~1lFrq6^@7!~qyC-M@k7#$M|(!o5#0pae=<@Nxw{DetL*cr&8@{d(oz==X`I5_CD>pD|s0<@Dav2~mRW`3pl;suTD zN3(0;6|yhX>oI4!<%fY&+z5tD-15_+D-2U5pIRjjHzMAQIsi7YN=J>POmr8^qU9S^ z;7cf%0|ol{C;RPRh1EY+*}Ef9V3yybF8@+U?Yy;L%gw%A#Z#&Q!TmCoE^v?i~GK{#J;2XCba*&g$?RB)=n&-|%5K zbF`jwCgT)$Jz=q;{a2|kwIokGsGxL7hv6L0=!#5klsXeZWrx;6+`_? zP~t?bO*Kj7^yLksHw#YQxFfkM5P}601Kzmag9pe~t7YMKRxO6IJ~5SISj~L?dS(&6$18#cI(t@ zcpL(M1R-JlwPGGG7D}fK9oJZ0=}m_L@9IdO}C?*bY%Lv@9Sfb2&2nC-+aI_dhsG#re&Q zWHv(NAZM1Q*A%dtPRV8HG`*t?Ve}Px!Rh2*&n0twZlvbW)o)W#V-GyEI=D%W)S+S! zbxXtA*Vh-V9~Fj8SMJC&aWn_CvtcR~mcR%X2r@qOZ1>vtFWF{i+`?rzDdd_Dfq;D2 zYys*Ym?LB-mzM4Q2+HW3_DA~8g|<~Gq>bBQf?%otsXIETD#{TIoP{$3()Pi`;1kr_ z1gIf@cWrIMyNt?{f&n19n(Ez|R|S%R+sefhnF&QYK|Ztr?}ulp?4~b0fCRxfMF9D6b?5IdY|r*)%YfG*+Gdrkfp< zDb-Mn$_jlYRM;)&r`b93)~>>#f(T(;c%(5Jjxgi16`` zTSk0TWn(D>`#Q_esVc_MoLhwi#9V;KQkQoC1YaVl6rU_EeWE)RdE*u~QZddGIE4UY z4A2@FnQseROg=$76`;LOT7QW6f&A%DKAFwKSX~FvHN$Py*+C9wogCzRp*r8IES;E` zt)cgL`g)1Z!QZX|afAptqd)l+&C9CMKhjDWu>9&5zBHjt8xv8xQi<{xG1# zXgJ)Vs`sAY&$6Kj&1HFRaPKY&uKn(^bg1!WpbX3ibh-hSIhDM>O_BRec$?OC(Cf&! zE%)c^_uZbK714N#5gXKK{nWH?^xK7dwWAU-)jXH~H?Yh}QJaOq(1vrmFbz?fjJU6!d9| z@hFCbpf3S|Tmp6cW}8o6EVE-T_1(xB@S!1Q1GC!U_C8yhNlMg!`G~_lA8vN{ zis5vEIT7!(PN2JnT;tyw?S!ljZsiC~1#YTYK^lV)OGk^;+M|W4RnKujTliBKS77*` z&q-!4Mz43~>bp9k1}VLV2o{=ax&eiFcvo;T_kHnt4DvR&TiewWXMJ{?{csa=GNo1M zX!F+5OQw<~$hlvflU-St^dONh^`J+Ly;;}yo)=(qiVPc zOcHhDbGIw)q$S&}xq^(tvJYKsSrY!532B2if{jp_DG){Wm7rjpdzP+SMVj#IK)it- z^Xc$aVxH=$;4NmWz{Swi}W=-=z+^{;_n zLzZrK#f+ZJDYqXa?*1JhfXgYx>3lI|y!e-M{Jjc${%FRK2HYKYin38`e==7zK;AE}QwV*&l4BqYAunuEE8cKCK|*TUTx@ zJ$$9;=Jo5%Mo+i1_+LVKMjjx%!N9u_crycUR{Y0`P$bMWl)F`?azk0KwCE^;2Gkj z;q^6cH2OrMz-PbN+d%hZr~g+L|Gm9^X4Q5I8xfIGFBC3PskMHPkjXgU{@HK%_9cv( zb&*S~UE7#e0x@ekq3@rdMO&PxCt9lOm5bfn*OKT-ZE2Tod6rn@Gp&T58k7gV{@lx6 zmidVT%4Z=Y{(mi4-)z7QMKEJ{eaMghlL&+o9%%U@1P6~0cArcop z_p{F$T`Gy^I++@}_{u$cnUHxdK+{HrbuXj-EM4m03?A(2bE(`qUrvB#II%LbWo{SYz9(rR$9s^Q8eZ*?{W9-uG_Ox$8}6<$!3+z{;avJ>Y?XI(e%9|l%1hi zNr2mDe+uuUxIDsXvP5Y8_xKbJ6zXBP{lopEiKSA8eV%=uIBeIX@nnBgdD(17t_J2z z`7jHhY=>a~FmAtD<9?Iv+#k3{mDvJLQ<*9U@xEy5uT?&^V!xr2>WjV-j~Z$HYP6rz z`Qt&u;E^A1qXH1G<8y0*U)NNTi9CmY>oy*SykWz;Q+SiZUnW?SoZBRcCXybhW!3RL z`eobIwr({CJzVA5t_)cJMS943ZI;-xsKPl$2hRBpj6)yWb$o5a%XNn zHnbGkm5W`8XsrO$yTEI;t|@!Xz}x24@j0uDn}yl)hO>jw_0-cS)?!fo;6-*yzaWF7 zh{}RINgkG1S>$NsI_q@}`N9N;WK@?^Y1(#i=Yga+ri}vBhX#RLdQ`#ky!$YFJC*4f zM5{%7y-;9k4j{q&=s&gOq{q+ixe9*`8#pzq!IYiuGQ5)%eF3^NH1;v)bPOYYTXM>L zkM;F$CNWg)gLTErMnsRNCii~j^AoEn`2Zudvr0DBnGt{c?LWVoNHd>vh2?hWmwMmz zc75|=Rwl-rNuB|@F`g5FlktC3_Sv(mV^c$}Z<;7C`<%La;CE+SJx$VJ8`t8}K(f~%^M;&!s-?&kcwd}c z6KF;3X_0V0(mo=iNBEhcv~7J=0jSvq)Z~qGw8Mbcn9j{j^%x_lkLkwV&3}Rin_G|io=_`0)e%=| z>=WRHBX&GEA;(EL$+w#1q|?saDehVORF#MV65eYcTUM;woboh`pg{5<3Y-9>dF29^ zT**PF>{;F3jo?{7>lYp*JV^d}pYp~7Z#?`D9uFdidjf85t53lL?y?aUY<1f-wRTsu zQN8{34h-D>CHmy!FX@E+b!`*)6A?UZ(6?tX>aSPp(t-{)45%%C?2p?G9S3PJ*ou&C|r}>s^*bdeG*1l$krluJ)c%r`oIRrwO|Ev#W*HSRs}4en@YlILm4>#Hbg7v zPrd_JKlie4J=f7jg=y6X#P2Zk2C(kE_!0VwiNAT5&0$ezBkGz9j z&R{J9{4iTr9Lb_{=WaQ4E#?t1G~pVl05qqmZ=rp&uRAk&7?jFADvtXi zqb-Kt7KbH88nxE3On(VgW4usY563GlTNm`}eEgG7ZOK#SVOOn5-O~|{AAzBl>C>CG zRZCBgQna0sT#U_(tVsER=aYvgZ!GcdBijw~UtZS!5D3#aYMs3hd2hu#p;* z8aT(T$DG=44x-V}s{~&lW+Qk-zDPv@EBoru-MyAEo{fh94*?zmJOp?M@DSi3z(atC U01p8k0z3qG2z+q_ME+#`2R|zhZ~y=R literal 0 HcmV?d00001 From b137edb625020135389ecc0104b7d0cc474cbdae Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Wed, 14 Feb 2024 00:07:29 -0800 Subject: [PATCH 11/29] fix format --- .../workflow/operators/loop/GeneratorOpDesc.scala | 6 +++--- .../workflow/operators/loop/GeneratorOpExec.scala | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala index e83f7bdd9f8..655f70f0b28 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala @@ -20,12 +20,12 @@ class RangeAttribute { @JsonProperty(required = true) @JsonSchemaTitle("Start") @JsonPropertyDescription("Start of the range") - var start: Int = 0 + var start: Double = 0 @JsonProperty(required = true) @JsonSchemaTitle("Step") @JsonPropertyDescription("Step of the range") - var step: Int = 1 + var step: Double = 1 } class GeneratorOpDesc extends SourceOperatorDescriptor { @@ -67,6 +67,6 @@ class GeneratorOpDesc extends SourceOperatorDescriptor { ) override def sourceSchema(): Schema = Schema.newBuilder - .add(attributes.map(attribute => new Attribute(attribute.name, AttributeType.INTEGER)).asJava) + .add(attributes.map(attribute => new Attribute(attribute.name, AttributeType.DOUBLE)).asJava) .build() } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala index 72c9ea13343..a7bd26a9c6e 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpExec.scala @@ -3,6 +3,7 @@ package edu.uci.ics.texera.workflow.operators.loop import edu.uci.ics.texera.workflow.common.operators.source.SourceOperatorExecutor import edu.uci.ics.texera.workflow.common.tuple.Tuple import edu.uci.ics.texera.workflow.common.tuple.schema.Schema +import scala.math.BigDecimal.double2bigDecimal class GeneratorOpExec( val iteration: Int, @@ -12,15 +13,15 @@ class GeneratorOpExec( override def produceTexeraTuple(): Iterator[Tuple] = { attributes .map(attribute => - (attribute.start until attribute.start + attribute.step * iteration by attribute.step).map( - i => (outputSchema.getAttribute(attribute.name), i) + (attribute.start until BigDecimal(attribute.start + attribute.step * iteration) by attribute.step).map( + i => (outputSchema.getAttribute(attribute.name), i.toDouble) ) ) .transpose - .map(x => { - val b = Tuple.newBuilder(outputSchema) - x.map(y => b.add(y._1, y._2)) - b.build() + .map(row => { + val builder = Tuple.newBuilder(outputSchema) + row.map(col => builder.add(col._1, col._2)) + builder.build() }) .iterator } From 869f049427bfaa5d7861f503709f6dae6c4ffd71 Mon Sep 17 00:00:00 2001 From: linxinyuan Date: Wed, 14 Feb 2024 03:29:47 -0800 Subject: [PATCH 12/29] update --- .../promisehandlers/ResumeLoopHandler.scala | 16 +++--- .../operators/loop/GeneratorOpDesc.scala | 9 ++-- .../operators/loop/LoopStartOpDesc.scala | 28 +++++------ .../operators/loop/LoopStartOpExec.scala | 46 +++++++----------- .../src/assets/operator_images/Generator.png | Bin 76268 -> 112049 bytes 5 files changed, 41 insertions(+), 58 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala index 785b2d8b07c..5083073f1f5 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala @@ -36,14 +36,14 @@ trait ResumeLoopHandler { registerHandler { (_: ResumeLoop, _) => { val ls = dp.operator.asInstanceOf[LoopStartOpExec] - if (ls.iteration < ls.termination) { - dp.processDataPayload( - loopToSelfChannelId, - DataFrame(ls.buffer.toArray ++ Array(EndOfIteration(dp.actorId))) - ) - } else { - dp.processDataPayload(loopToSelfChannelId, EndOfUpstream()) - } + //if (ls.iteration < ls.termination) { + // dp.processDataPayload( + // loopToSelfChannelId, + // DataFrame(ls.buffer.toArray ++ Array(EndOfIteration(dp.actorId))) + // ) + //} else { + // dp.processDataPayload(loopToSelfChannelId, EndOfUpstream()) + // } } } } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala index 655f70f0b28..69d3be71f01 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/GeneratorOpDesc.scala @@ -15,14 +15,14 @@ class RangeAttribute { @JsonProperty(required = true) @JsonSchemaTitle("Name") @JsonPropertyDescription("Attribute name in the schema") - var name: String = "" + var name: String = "Iteration" - @JsonProperty(required = true) + @JsonProperty(defaultValue = "0", required = true) @JsonSchemaTitle("Start") @JsonPropertyDescription("Start of the range") var start: Double = 0 - @JsonProperty(required = true) + @JsonProperty(defaultValue = "1", required = true) @JsonSchemaTitle("Step") @JsonPropertyDescription("Step of the range") var step: Double = 1 @@ -65,8 +65,7 @@ class GeneratorOpDesc extends SourceOperatorDescriptor { outputPorts = List(OutputPort()), supportReconfiguration = true ) - override def sourceSchema(): Schema = - Schema.newBuilder + override def sourceSchema(): Schema = Schema.newBuilder .add(attributes.map(attribute => new Attribute(attribute.name, AttributeType.DOUBLE)).asJava) .build() } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala index 54a958bc1fd..4affcd10161 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala @@ -5,21 +5,15 @@ import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle import edu.uci.ics.amber.engine.architecture.deploysemantics.PhysicalOp import edu.uci.ics.amber.engine.architecture.deploysemantics.layer.OpExecInitInfo import edu.uci.ics.amber.engine.common.virtualidentity.{ExecutionIdentity, WorkflowIdentity} -import edu.uci.ics.amber.engine.common.workflow.{InputPort, OutputPort} +import edu.uci.ics.amber.engine.common.workflow.{InputPort, OutputPort, PortIdentity} import edu.uci.ics.texera.workflow.common.metadata.{OperatorGroupConstants, OperatorInfo} import edu.uci.ics.texera.workflow.common.operators.LogicalOp import edu.uci.ics.texera.workflow.common.tuple.schema.{AttributeType, Schema} class LoopStartOpDesc extends LogicalOp { - - @JsonProperty(required = true) - @JsonSchemaTitle("Iteration") - @JsonPropertyDescription("the max number of iterations") - var termination: Int = _ - @JsonProperty(defaultValue = "false") - @JsonSchemaTitle("Append Iteration Number") - var append: Boolean = false + @JsonSchemaTitle("Attach Control to Data") + var attach: Boolean = false override def getPhysicalOp( workflowId: WorkflowIdentity, @@ -32,7 +26,7 @@ class LoopStartOpDesc extends LogicalOp { executionId, operatorIdentifier, OpExecInitInfo((_, _, operatorConfig) => { - new LoopStartOpExec(outputSchema, operatorConfig.workerConfigs.head.workerId, termination) + new LoopStartOpExec(outputSchema, operatorConfig.workerConfigs.head.workerId) }) ) .withInputPorts(operatorInfo.inputPorts, inputPortToSchemaMapping) @@ -45,21 +39,23 @@ class LoopStartOpDesc extends LogicalOp { "LoopStart", "Limit the number of output rows", OperatorGroupConstants.CONTROL_GROUP, - inputPorts = List(InputPort()), + inputPorts = List( + InputPort(PortIdentity(0), displayName = "Control", dependencies = List(PortIdentity(1))), + InputPort(PortIdentity(1), displayName = "Data") + ), outputPorts = List(OutputPort()), supportReconfiguration = true ) - override def getOutputSchema(schemas: Array[Schema]): Schema = { - if (append) { + override def getOutputSchema(schemas: Array[Schema]): Schema = + if (attach) { Schema .newBuilder() .add("Iteration", AttributeType.INTEGER) .add(schemas(0)) + .add(schemas(1)) .build() } else { - schemas(0) + schemas(1) } - } - } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala index 625ac545aca..976e113ad69 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala @@ -15,11 +15,10 @@ import scala.collection.mutable class LoopStartOpExec( val outputSchema: Schema, - val workerId: ActorVirtualIdentity, - val termination: Int + val workerId: ActorVirtualIdentity ) extends OperatorExecutor { var iteration = 0 - var buffer = new mutable.ArrayBuffer[ITuple] + var data = new mutable.ArrayBuffer[ITuple] override def processTuple( tuple: Either[ITuple, InputExhausted], input: Int, @@ -28,37 +27,26 @@ class LoopStartOpExec( ): Iterator[(ITuple, Option[PortIdentity])] = { tuple match { case Left(t) => - t match { - case t: EndOfIteration => + input match { + case 0 => iteration += 1 - Iterator((t, None)) - case t => - if (iteration == 0) { - buffer.append(t) - } if (outputSchema.containsAttribute("Iteration")) { - Iterator( - ( - Tuple - .newBuilder(outputSchema) - .add(outputSchema.getAttribute("Iteration"), iteration) - .add(t.asInstanceOf[Tuple]) - .build, - None - ) - ) + data.iterator.map(dt => ( + Tuple.newBuilder(outputSchema).add(outputSchema.getAttribute("Iteration"), iteration-1) + .add(dt.asInstanceOf[Tuple]) + .add(t.asInstanceOf[Tuple]).build + , + None + )) } else { - Iterator((t, None)) + data.iterator.map(t => (t, None)) } + case 1 => + data.append(t) + Iterator.empty } - - case Right(_) => - if (iteration == 0) { - iteration += 1 - Iterator((EndOfIteration(workerId), None)) - } else { - Iterator.empty - } + case Right(_) => Iterator.empty + //Iterator((EndOfIteration(workerId), None)) } } diff --git a/core/new-gui/src/assets/operator_images/Generator.png b/core/new-gui/src/assets/operator_images/Generator.png index d77af3e7735a45db7d5a2d9c3b0fd10e5b1ccb48..99cf778efe01de5b7d69897acea59c2c2941f741 100644 GIT binary patch literal 112049 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

KzoiaO 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 literal 76268 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 U01p8k0z3qG2z+q_ME+#`2R|zhZ~y=R From 4bb5e1ef564419ac8164f05f8161a127a135d41a Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Fri, 16 Feb 2024 14:47:59 -0800 Subject: [PATCH 13/29] fix conflict --- .../architecture/worker/DataProcessor.scala | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index 7751e2f3fc1..81912580440 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -165,37 +165,7 @@ class DataProcessor( adaptiveBatchingMonitor.resumeAdaptiveBatching() } - /** provide API for actor to get stats of this operator - * - * @return (input tuple count, output tuple count) - */ - def collectStatistics(): WorkerStatistics = { - // sink operator doesn't output to downstream so internal count is 0 - // but for user-friendliness we show its input count as output count - val displayOut = operator match { - case sink: ISinkOperatorExecutor => - inputTupleCount - case _ => - outputTupleCount - } - var loopI = 0 - operator match { - case exec: LoopStartOpExec => - loopI = exec.iteration - case exec: LoopEndOpExec => - loopI = exec.iteration - case _ => //do nothing - } - WorkerStatistics( - stateManager.getCurrentState, - inputTupleCount, - displayOut, - dataProcessingTime, - controlProcessingTime, - totalExecutionTime - dataProcessingTime - controlProcessingTime, - loopI - ) - } + def collectStatistics(): WorkerStatistics = statisticsManager.getStatistics(stateManager.getCurrentState, operator) From 46147ea2a9eed1d16dbcad1a49ce3c19bb5f7c4e Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Fri, 16 Feb 2024 14:49:56 -0800 Subject: [PATCH 14/29] fix conflict --- .../amber/engine/architecture/worker/DataProcessor.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index 81912580440..6a98803ede1 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -165,6 +165,10 @@ class DataProcessor( adaptiveBatchingMonitor.resumeAdaptiveBatching() } + /** provide API for actor to get stats of this operator + * + * @return (input tuple count, output tuple count) + */ def collectStatistics(): WorkerStatistics = statisticsManager.getStatistics(stateManager.getCurrentState, operator) @@ -185,8 +189,6 @@ class DataProcessor( ) ) // logger.info(s"$tuple, $inputTupleCount, $outputTupleCount") - if (tuple.isLeft && !tuple.left.get.isInstanceOf[EndOfIteration]) { - inputTupleCount += 1 if (tuple.isLeft) { statisticsManager.increaseInputTupleCount() } From 47a859d46c0f386cbdde547403a9f638e053d648 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Fri, 16 Feb 2024 14:50:24 -0800 Subject: [PATCH 15/29] fix conflict --- .../uci/ics/amber/engine/architecture/worker/DataProcessor.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index 6a98803ede1..9c1904948fd 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -188,7 +188,6 @@ class DataProcessor( asyncRPCClient ) ) - // logger.info(s"$tuple, $inputTupleCount, $outputTupleCount") if (tuple.isLeft) { statisticsManager.increaseInputTupleCount() } From 03ca2f6fe5c40ec0d3fae866f7ccbaeeef58063c Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Fri, 16 Feb 2024 14:52:41 -0800 Subject: [PATCH 16/29] fix conflict --- .../architecture/worker/managers/StatisticsManager.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/managers/StatisticsManager.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/managers/StatisticsManager.scala index 56c9ef0cd88..ac9882ea754 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/managers/StatisticsManager.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/managers/StatisticsManager.scala @@ -29,7 +29,8 @@ class StatisticsManager { displayOut, dataProcessingTime, controlProcessingTime, - totalExecutionTime - dataProcessingTime - controlProcessingTime + totalExecutionTime - dataProcessingTime - controlProcessingTime, + 0 ) } From e0c392b368aed1208e16edf416940494889f4405 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Tue, 20 Feb 2024 16:01:52 -0800 Subject: [PATCH 17/29] fix conflict --- .../layer/WorkerExecution.scala | 2 +- .../workflow/common/operators/LogicalOp.scala | 21 ++---- .../operators/loop/LoopStartOpDesc.scala | 2 +- .../operators/loop/LoopStartOpExec.scala | 1 - .../operators/loop/LoopStartV2OpDesc.scala | 61 +++++++++++++++++ .../operators/loop/LoopStartV2OpExec.scala | 63 ++++++++++++++++++ .../assets/operator_images/LoopStartV2.png | Bin 0 -> 2138 bytes 7 files changed, 131 insertions(+), 19 deletions(-) create mode 100644 core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartV2OpDesc.scala create mode 100644 core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartV2OpExec.scala create mode 100644 core/new-gui/src/assets/operator_images/LoopStartV2.png diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/deploysemantics/layer/WorkerExecution.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/deploysemantics/layer/WorkerExecution.scala index f4c7c5b3863..b4369eb6726 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/deploysemantics/layer/WorkerExecution.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/deploysemantics/layer/WorkerExecution.scala @@ -17,7 +17,7 @@ case class WorkerExecution() extends Serializable { private var state: WorkerState = UNINITIALIZED // TODO: move stats onto ports, and make this as an aggregation func. // TODO: separate state from stats - private var stats: WorkerStatistics = WorkerStatistics(UNINITIALIZED, 0, 0, 0, 0, 0) + private var stats: WorkerStatistics = WorkerStatistics(UNINITIALIZED, 0, 0, 0, 0, 0, 0) def getState: WorkerState = state diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala index ec920495563..d0fbe10cfbc 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala @@ -4,11 +4,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes.Type import com.fasterxml.jackson.annotation.{JsonIgnore, JsonProperty, JsonSubTypes, JsonTypeInfo} import edu.uci.ics.amber.engine.architecture.deploysemantics.PhysicalOp import edu.uci.ics.amber.engine.common.IOperatorExecutor -import edu.uci.ics.amber.engine.common.virtualidentity.{ - ExecutionIdentity, - OperatorIdentity, - WorkflowIdentity -} +import edu.uci.ics.amber.engine.common.virtualidentity.{ExecutionIdentity, OperatorIdentity, WorkflowIdentity} import edu.uci.ics.amber.engine.common.workflow.PortIdentity import edu.uci.ics.texera.web.OPversion import edu.uci.ics.texera.workflow.common.metadata.{OperatorInfo, PropertyNameConstants} @@ -28,7 +24,7 @@ import edu.uci.ics.texera.workflow.operators.intervalJoin.IntervalJoinOpDesc import edu.uci.ics.texera.workflow.operators.keywordSearch.KeywordSearchOpDesc import edu.uci.ics.texera.workflow.operators.limit.LimitOpDesc import edu.uci.ics.texera.workflow.operators.linearregression.LinearRegressionOpDesc -import edu.uci.ics.texera.workflow.operators.loop.{GeneratorOpDesc, LoopEndOpDesc, LoopStartOpDesc} +import edu.uci.ics.texera.workflow.operators.loop.{GeneratorOpDesc, LoopEndOpDesc, LoopStartOpDesc, LoopStartV2OpDesc} import edu.uci.ics.texera.workflow.operators.projection.ProjectionOpDesc import edu.uci.ics.texera.workflow.operators.randomksampling.RandomKSamplingOpDesc import edu.uci.ics.texera.workflow.operators.regex.RegexOpDesc @@ -37,10 +33,7 @@ import edu.uci.ics.texera.workflow.operators.sentiment.SentimentAnalysisOpDesc import edu.uci.ics.texera.workflow.operators.sink.managed.ProgressiveSinkOpDesc import edu.uci.ics.texera.workflow.operators.sortPartitions.SortPartitionsOpDesc import edu.uci.ics.texera.workflow.operators.source.apis.reddit.RedditSearchSourceOpDesc -import edu.uci.ics.texera.workflow.operators.source.apis.twitter.v2.{ - TwitterFullArchiveSearchSourceOpDesc, - TwitterSearchSourceOpDesc -} +import edu.uci.ics.texera.workflow.operators.source.apis.twitter.v2.{TwitterFullArchiveSearchSourceOpDesc, TwitterSearchSourceOpDesc} import edu.uci.ics.texera.workflow.operators.source.fetcher.URLFetcherOpDesc import edu.uci.ics.texera.workflow.operators.source.scan.FileScanSourceOpDesc import edu.uci.ics.texera.workflow.operators.source.scan.csv.CSVScanSourceOpDesc @@ -54,12 +47,7 @@ import edu.uci.ics.texera.workflow.operators.split.SplitOpDesc import edu.uci.ics.texera.workflow.operators.symmetricDifference.SymmetricDifferenceOpDesc import edu.uci.ics.texera.workflow.operators.typecasting.TypeCastingOpDesc import edu.uci.ics.texera.workflow.operators.udf.python.source.PythonUDFSourceOpDescV2 -import edu.uci.ics.texera.workflow.operators.udf.python.{ - DualInputPortsPythonUDFOpDescV2, - PythonLambdaFunctionOpDesc, - PythonTableReducerOpDesc, - PythonUDFOpDescV2 -} +import edu.uci.ics.texera.workflow.operators.udf.python.{DualInputPortsPythonUDFOpDescV2, PythonLambdaFunctionOpDesc, PythonTableReducerOpDesc, PythonUDFOpDescV2} import edu.uci.ics.texera.workflow.operators.union.UnionOpDesc import edu.uci.ics.texera.workflow.operators.unneststring.UnnestStringOpDesc import edu.uci.ics.texera.workflow.operators.visualization.boxPlot.BoxPlotOpDesc @@ -137,6 +125,7 @@ trait StateTransferFunc new Type(value = classOf[TypeCastingOpDesc], name = "TypeCasting"), new Type(value = classOf[LimitOpDesc], name = "Limit"), new Type(value = classOf[LoopStartOpDesc], name = "LoopStart"), + new Type(value = classOf[LoopStartV2OpDesc], name = "LoopStartV2"), new Type(value = classOf[GeneratorOpDesc], name = "Generator"), new Type(value = classOf[LoopEndOpDesc], name = "LoopEnd"), new Type(value = classOf[RandomKSamplingOpDesc], name = "RandomKSampling"), diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala index 4affcd10161..ef5beb71e9a 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala @@ -1,6 +1,6 @@ package edu.uci.ics.texera.workflow.operators.loop -import com.fasterxml.jackson.annotation.{JsonProperty, JsonPropertyDescription} +import com.fasterxml.jackson.annotation.JsonProperty import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle import edu.uci.ics.amber.engine.architecture.deploysemantics.PhysicalOp import edu.uci.ics.amber.engine.architecture.deploysemantics.layer.OpExecInitInfo diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala index 976e113ad69..c09cae37b2a 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala @@ -1,6 +1,5 @@ package edu.uci.ics.texera.workflow.operators.loop -import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.EndOfIteration import edu.uci.ics.amber.engine.architecture.worker.PauseManager import edu.uci.ics.amber.engine.common.InputExhausted import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartV2OpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartV2OpDesc.scala new file mode 100644 index 00000000000..d1585d0b850 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartV2OpDesc.scala @@ -0,0 +1,61 @@ +package edu.uci.ics.texera.workflow.operators.loop + +import com.fasterxml.jackson.annotation.JsonProperty +import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle +import edu.uci.ics.amber.engine.architecture.deploysemantics.PhysicalOp +import edu.uci.ics.amber.engine.architecture.deploysemantics.layer.OpExecInitInfo +import edu.uci.ics.amber.engine.common.virtualidentity.{ExecutionIdentity, WorkflowIdentity} +import edu.uci.ics.amber.engine.common.workflow.{InputPort, OutputPort, PortIdentity} +import edu.uci.ics.texera.workflow.common.metadata.{OperatorGroupConstants, OperatorInfo} +import edu.uci.ics.texera.workflow.common.operators.LogicalOp +import edu.uci.ics.texera.workflow.common.tuple.schema.{AttributeType, Schema} + +class LoopStartV2OpDesc extends LogicalOp { + @JsonProperty(defaultValue = "false") + @JsonSchemaTitle("Attach Control to Data") + var attach: Boolean = false + + override def getPhysicalOp( + workflowId: WorkflowIdentity, + executionId: ExecutionIdentity + ): PhysicalOp = { + val outputSchema = outputPortToSchemaMapping(operatorInfo.outputPorts.head.id) + PhysicalOp + .oneToOnePhysicalOp( + workflowId, + executionId, + operatorIdentifier, + OpExecInitInfo((_, _, operatorConfig) => { + new LoopStartV2OpExec(outputSchema, operatorConfig.workerConfigs.head.workerId) + }) + ) + .withInputPorts(operatorInfo.inputPorts, inputPortToSchemaMapping) + .withOutputPorts(operatorInfo.outputPorts, outputPortToSchemaMapping) + .withSuggestedWorkerNum(1) + } + + override def operatorInfo: OperatorInfo = + OperatorInfo( + "LoopStartV2", + "Limit the number of output rows", + OperatorGroupConstants.CONTROL_GROUP, + inputPorts = List( + InputPort(PortIdentity(0), displayName = "Control", dependencies = List(PortIdentity(1))), + InputPort(PortIdentity(1), displayName = "Data") + ), + outputPorts = List(OutputPort()), + supportReconfiguration = true + ) + + override def getOutputSchema(schemas: Array[Schema]): Schema = + if (attach) { + Schema + .newBuilder() + .add("Iteration", AttributeType.INTEGER) + .add(schemas(0)) + .add(schemas(1)) + .build() + } else { + schemas(1) + } +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartV2OpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartV2OpExec.scala new file mode 100644 index 00000000000..ff1d9616e2a --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartV2OpExec.scala @@ -0,0 +1,63 @@ +package edu.uci.ics.texera.workflow.operators.loop + +import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.EndOfIteration +import edu.uci.ics.amber.engine.architecture.worker.PauseManager +import edu.uci.ics.amber.engine.common.InputExhausted +import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient +import edu.uci.ics.amber.engine.common.tuple.ITuple +import edu.uci.ics.amber.engine.common.virtualidentity.ActorVirtualIdentity +import edu.uci.ics.amber.engine.common.workflow.PortIdentity +import edu.uci.ics.texera.workflow.common.operators.OperatorExecutor +import edu.uci.ics.texera.workflow.common.tuple.Tuple +import edu.uci.ics.texera.workflow.common.tuple.schema.Schema + +import scala.collection.mutable + +class LoopStartV2OpExec( + val outputSchema: Schema, + val workerId: ActorVirtualIdentity +) extends OperatorExecutor { + var iteration = 0 + var data = new mutable.ArrayBuffer[ITuple] + override def processTuple( + tuple: Either[ITuple, InputExhausted], + input: Int, + pauseManager: PauseManager, + asyncRPCClient: AsyncRPCClient + ): Iterator[(ITuple, Option[PortIdentity])] = { + tuple match { + case Left(t) => + input match { + case 0 => + iteration += 1 + if (outputSchema.containsAttribute("Iteration")) { + data.iterator.map(dt => ( + Tuple.newBuilder(outputSchema).add(outputSchema.getAttribute("Iteration"), iteration-1) + .add(dt.asInstanceOf[Tuple]) + .add(t.asInstanceOf[Tuple]).build + , + None + )) + } else { + data.iterator.map(t => (t, None)) + } + case 1 => + data.append(t) + Iterator.empty + } + case Right(_) => Iterator.empty + //Iterator((EndOfIteration(workerId), None)) + } + } + + override def open(): Unit = {} + + override def close(): Unit = {} + + override def processTexeraTuple( + tuple: Either[Tuple, InputExhausted], + input: Int, + pauseManager: PauseManager, + asyncRPCClient: AsyncRPCClient + ): Iterator[Tuple] = ??? +} diff --git a/core/new-gui/src/assets/operator_images/LoopStartV2.png b/core/new-gui/src/assets/operator_images/LoopStartV2.png new file mode 100644 index 0000000000000000000000000000000000000000..7e5be023cdf6b64dd1bc140e5d75980455afeefb GIT binary patch literal 2138 zcmV-g2&MOlP)Px-6iGxuRCr$Pok@1vFbsy}TQ#e+JKw6wt+cDNN^a$bl4voq27m;>LBHSYqAkVY z$0rb+#7kG<@A`TD-M{p=3agGPM=EfMd@k!_*RSip?%sX$Fa2BAilA^a1?mO0>wb6l z?j5k2v68i*x_G;KNw`$Lu656Tc9Eo zuwW)j8SQ}zShIknU1*!20#9EcQB z6cz^>1a(cxTka+6Y@h~k>Vdf?xiG9J0X0-|fOc49G29qr3Tieum%s|bdO}cVHSgWK zRd`?mYB7L@zzV^7YEZqNBe1F-Ru$AD0sZM+llKS!)Id<7mwzo{Qv-F_Gg`;Ajf=V5 zcEPBP*Z_3}D+OT1L9Nzsk<(YzwwE$XlA1;kSW!?b@}lb|h!7T_9?%@2san@xK`cu5 z4NPGHDnVMNrYfLLkh*xZogYJ3fJ!!d(!e#T>u=Ub0F_K1J7MHlAR1IaI-00i9oPgi z9*BT73e;we!|nF0U1^@ zs7;RU1&&!a`tnAT$9|9)ZqY&gy58B9DER@RKu1jgs7DGB{-0nA5~$U$I4!jz2t$w; zqkgJLlynDUghNo9^B97VVtR@~AU#e0tKZ~t7t{tJ75|b-a~jdPl25tgMD{_gXRF?k zXh|dcz09jVg*qRav^@^AC+J29unB4*p2DAq9F9D=pW8RejG4((AV;*f{{Mr`-7oK{ z+EsgPgPL!YKX^Y6rKgkdgTv61*9U7K6zNnkYM3epFlN!gQDWcT~H&}?8PnHV1_x_cm~zVnY^)I>;EKn0{3-V~!vN)Oxq z1Xa7+7l8B!s6zQ=uyYedn!q}B{|`_Nx_vRvhYQil;ar^?Yf$$#9B^gF6_Z@Wbki%x ztAm4^J*sB6k1kr5l+3wnhXBsW4WL>Slvzs@udINoSG{+mM^%k8;7yegDI0JR1JDygFuPXM(N0BWhGCaCVhT#aw=V=z?de(toRjZVSEj!2b;lCwiOAI&NI40$xA|c}btX{twu`E7^XstQe;!h`;?wV2 z%Jo4-_kRvhHM@QHbw`UcQ|SIrz^bA*?VPi-I)#csRl~5VdQ*UDgxEX`@Jqdlp9+)We4}*#tzgGR)hetLR`( zU|H*=iJ9@^;87n?j-wUVwYy&yI3H%)=vntJ?akv=w?Xq4_}$+EM50(-LrH< z#{4!zlKpGYwa)zOG_vem6=HDBqaRts+|g(kR99Km4F_4&$W*8>sEl|tQ-hhs9_Vun z>gTnz5P+#bA;|?nZ4OexEW{zGgI7fekWRZ0s4d!pa$Jc7Dqv0LRAQSWb7U`2X@e|? zo0Q0)PVs8qDJuir)QU*(sEv*6XNeh_us{W@2^ces5|+~3$lf=OsRtia46fp6Wg4dG za4SL!HmH+;m@# zDsgnbrDHOvO~+D)^f3}JKy6IZ+tl4B?Q;+TwJ~8C^f8h$L2X1+(!N)UiejKPA}mF- zxHJ)4oya1?$hF7Y9dSk14)CI8Pk&?3lD+Fpmu$lmB4(g-(a!au8T+XF? z&3-<|(l-l&nh{o0IL%^I3J3n{a?L|445|^V{+yco^kdRID20W+G-h2trJ@70V|d`a zt^4IY5lN{uHH`wO6RauwE@t@doj)}B__y3}7UfZ?b`FmA)(Wr6Db&ar0hK8vsx4#E z73EQ58h~eJ1sL0ssI2 literal 0 HcmV?d00001 From d7936fd2c2e0682f946a4195fa228d317582d352 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Wed, 21 Feb 2024 16:43:14 -0800 Subject: [PATCH 18/29] update --- .../promisehandlers/ForLoopHandler.scala | 5 +- .../messaginglayer/AmberFIFOChannel.scala | 6 ++- .../RegionExecutionController.scala | 16 +++--- .../architecture/worker/DataProcessor.scala | 16 +++--- .../promisehandlers/ResumeLoopHandler.scala | 36 ++++++------- .../engine/common/ISinkOperatorExecutor.scala | 7 +++ .../common/operators/OperatorExecutor.scala | 5 +- .../operators/loop/LoopEndOpDesc.scala | 2 +- .../operators/loop/LoopEndOpExec.scala | 37 +++++++++---- .../operators/loop/LoopStartOpDesc.scala | 2 +- .../operators/loop/LoopStartOpExec.scala | 53 +++++++++++++------ 11 files changed, 113 insertions(+), 72 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/ForLoopHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/ForLoopHandler.scala index ad8179976a2..9a506531877 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/ForLoopHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/ForLoopHandler.scala @@ -7,13 +7,12 @@ import edu.uci.ics.amber.engine.common.rpc.AsyncRPCServer.ControlCommand import edu.uci.ics.amber.engine.common.virtualidentity.ActorVirtualIdentity object ForLoopHandler { - final case class IterationCompleted(workerId: ActorVirtualIdentity) extends ControlCommand[Unit] + final case class IterationCompleted(startWorkerId: ActorVirtualIdentity, endWorkerId: ActorVirtualIdentity) extends ControlCommand[Unit] } trait ForLoopHandler { this: ControllerAsyncRPCHandlerInitializer => - registerHandler { (msg: IterationCompleted, _) => - send(ResumeLoop(), msg.workerId) + send(ResumeLoop(msg.startWorkerId, msg.endWorkerId), msg.startWorkerId) } } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/messaginglayer/AmberFIFOChannel.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/messaginglayer/AmberFIFOChannel.scala index 702c97b764d..c8041d41de7 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/messaginglayer/AmberFIFOChannel.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/messaginglayer/AmberFIFOChannel.scala @@ -98,6 +98,10 @@ class AmberFIFOChannel(val channelId: ChannelIdentity) extends AmberLogging { } def getPortId: PortIdentity = { - this.portId.get + if(this.portId.isEmpty){ + PortIdentity() + }else{ + this.portId.get + } } } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/scheduling/RegionExecutionController.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/scheduling/RegionExecutionController.scala index dda2f5110a9..f3d9c87680e 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/scheduling/RegionExecutionController.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/scheduling/RegionExecutionController.scala @@ -3,14 +3,8 @@ package edu.uci.ics.amber.engine.architecture.scheduling import com.twitter.util.Future import edu.uci.ics.amber.engine.architecture.common.AkkaActorService import edu.uci.ics.amber.engine.architecture.controller.ControllerConfig -import edu.uci.ics.amber.engine.architecture.controller.ControllerEvent.{ - WorkerAssignmentUpdate, - WorkflowStatsUpdate -} -import edu.uci.ics.amber.engine.architecture.controller.execution.{ - OperatorExecution, - WorkflowExecution -} +import edu.uci.ics.amber.engine.architecture.controller.ControllerEvent.{WorkerAssignmentUpdate, WorkflowStatsUpdate} +import edu.uci.ics.amber.engine.architecture.controller.execution.{OperatorExecution, WorkflowExecution} import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.FatalErrorHandler.FatalError import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.LinkWorkersHandler.LinkWorkers import edu.uci.ics.amber.engine.architecture.deploysemantics.PhysicalOp @@ -18,12 +12,13 @@ import edu.uci.ics.amber.engine.architecture.pythonworker.promisehandlers.Initia import edu.uci.ics.amber.engine.architecture.scheduling.config.OperatorConfig import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.AssignPortHandler.AssignPort import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.OpenOperatorHandler.OpenOperator +import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.ResumeLoopHandler import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.StartHandler.StartWorker import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient import edu.uci.ics.amber.engine.common.virtualidentity.util.CONTROLLER import edu.uci.ics.amber.engine.common.workflow.PhysicalLink import edu.uci.ics.texera.web.workflowruntimestate.WorkflowAggregatedState - +import scala.collection.mutable import scala.collection.Seq class RegionExecutionController( region: Region, @@ -169,8 +164,9 @@ class RegionExecutionController( } private def connectChannels(links: Set[PhysicalLink]): Future[Seq[Unit]] = { + val additionalLinks = mutable.HashSet[PhysicalLink]() Future.collect( - links.map { link: PhysicalLink => asyncRPCClient.send(LinkWorkers(link), CONTROLLER) }.toSeq + (additionalLinks ++ links).map { link: PhysicalLink => asyncRPCClient.send(LinkWorkers(link), CONTROLLER) }.toSeq ) } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index 7d0a1ea0d5b..d3f5be010cc 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -65,7 +65,9 @@ object DataProcessor { } case class FinalizePort(portId: PortIdentity, input: Boolean) extends SpecialDataTuple case class FinalizeOperator() extends SpecialDataTuple - case class EndOfIteration(workerId: ActorVirtualIdentity) extends SpecialDataTuple + + case class StartOfIteration(workerId: ActorVirtualIdentity) extends SpecialDataTuple + case class EndOfIteration(startWorkerId: ActorVirtualIdentity, endWorkerId: ActorVirtualIdentity) extends SpecialDataTuple class DPOutputIterator extends Iterator[(ITuple, Option[PortIdentity])] { val queue = new mutable.Queue[(ITuple, Option[PortIdentity])] @@ -208,8 +210,8 @@ class DataProcessor( if (outputTuple == null) return outputTuple match { - case EndOfIteration(workerId) => - asyncRPCClient.send(IterationCompleted(workerId), CONTROLLER) + case EndOfIteration(startWorkerId, endWorkerId) => + asyncRPCClient.send(IterationCompleted(startWorkerId, endWorkerId), CONTROLLER) case FinalizeOperator() => outputManager.emitEndOfUpstream() // Send Completed signal to worker actor. @@ -282,10 +284,6 @@ class DataProcessor( initBatch(channelId, tuples) processInputTuple(Left(inputBatch(currentInputIdx))) case EndOfUpstream() => -// if (operator.isInstanceOf[LoopStartOpExec]) { -// processInputTuple(Right(InputExhausted())) -// return -// } val channel = this.inputGateway.getChannel(channelId) val portId = channel.getPortId @@ -303,7 +301,9 @@ class DataProcessor( .foreach(outputPortId => outputIterator.appendSpecialTupleToEnd(FinalizePort(outputPortId, input = false)) ) - outputIterator.appendSpecialTupleToEnd(FinalizeOperator()) + if(!operator.isInstanceOf[LoopStartOpExec]){ + outputIterator.appendSpecialTupleToEnd(FinalizeOperator()) + } } } statisticsManager.increaseDataProcessingTime(System.nanoTime() - dataProcessingStartTime) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala index 5083073f1f5..c9782961806 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/ResumeLoopHandler.scala @@ -1,31 +1,17 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers -import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.EndOfIteration -import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.ResumeLoopHandler.{ - ResumeLoop, - loopToSelfChannelId -} +import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{EndOfIteration, StartOfIteration} +import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.ResumeLoopHandler.{ResumeLoop, loopToSelfChannelId} import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer import edu.uci.ics.amber.engine.common.ambermessage.{DataFrame, EndOfUpstream} import edu.uci.ics.amber.engine.common.rpc.AsyncRPCServer.ControlCommand -import edu.uci.ics.amber.engine.common.virtualidentity.{ - ActorVirtualIdentity, - ChannelIdentity, - OperatorIdentity, - PhysicalOpIdentity -} +import edu.uci.ics.amber.engine.common.virtualidentity.{ActorVirtualIdentity, ChannelIdentity, OperatorIdentity, PhysicalOpIdentity} import edu.uci.ics.amber.engine.common.workflow.{PhysicalLink, PortIdentity} import edu.uci.ics.texera.workflow.operators.loop.LoopStartOpExec object ResumeLoopHandler { - final case class ResumeLoop() extends ControlCommand[Unit] + final case class ResumeLoop(startWorkerId: ActorVirtualIdentity, endWorkerId: ActorVirtualIdentity) extends ControlCommand[Unit] - val loopToSelfLink = PhysicalLink( - ResumeLoopHandler.loopSelfOp, - PortIdentity(), - ResumeLoopHandler.loopSelfOp, - PortIdentity() - ) val loopSelfOp = PhysicalOpIdentity(OperatorIdentity("loopSelf"), "loopSelf") val loopSelf = ActorVirtualIdentity("loopSelf") val loopToSelfChannelId = ChannelIdentity(loopSelf, loopSelf, isControl = false) @@ -33,9 +19,19 @@ object ResumeLoopHandler { trait ResumeLoopHandler { this: DataProcessorRPCHandlerInitializer => - registerHandler { (_: ResumeLoop, _) => + registerHandler { (msg: ResumeLoop, _) => { - val ls = dp.operator.asInstanceOf[LoopStartOpExec] + //val ls = dp.operator.asInstanceOf[LoopStartOpExec] + dp.processDataPayload( + loopToSelfChannelId, + DataFrame(Array(StartOfIteration(dp.actorId) + ))) +// val loopStartToEndLink: PhysicalLink = PhysicalLink( +// msg.startWorkerId, +// PortIdentity(), +// ResumeLoopHandler.loopSelfOp, +// PortIdentity() +// ) //if (ls.iteration < ls.termination) { // dp.processDataPayload( // loopToSelfChannelId, diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/ISinkOperatorExecutor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/ISinkOperatorExecutor.scala index 73ccbb072b7..e63f501f571 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/ISinkOperatorExecutor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/ISinkOperatorExecutor.scala @@ -1,5 +1,6 @@ package edu.uci.ics.amber.engine.common +import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{EndOfIteration, StartOfIteration} import edu.uci.ics.amber.engine.architecture.worker.PauseManager import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient import edu.uci.ics.amber.engine.common.tuple.ITuple @@ -13,6 +14,12 @@ trait ISinkOperatorExecutor extends IOperatorExecutor { pauseManager: PauseManager, asyncRPCClient: AsyncRPCClient ): Iterator[(ITuple, Option[PortIdentity])] = { + if (tuple.isLeft && tuple.left.get.isInstanceOf[StartOfIteration]) { + return Iterator.empty + } + if (tuple.isLeft && tuple.left.get.isInstanceOf[EndOfIteration]) { + return Iterator.empty + } consume(tuple, input) Iterator.empty } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/OperatorExecutor.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/OperatorExecutor.scala index 79e096fcc2e..17bccaf09a5 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/OperatorExecutor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/OperatorExecutor.scala @@ -1,6 +1,6 @@ package edu.uci.ics.texera.workflow.common.operators -import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.EndOfIteration +import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{EndOfIteration, StartOfIteration} import edu.uci.ics.amber.engine.architecture.worker.PauseManager import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient import edu.uci.ics.amber.engine.common.{IOperatorExecutor, InputExhausted} @@ -16,6 +16,9 @@ trait OperatorExecutor extends IOperatorExecutor { pauseManager: PauseManager, asyncRPCClient: AsyncRPCClient ): Iterator[(ITuple, Option[PortIdentity])] = { + if (tuple.isLeft && tuple.left.get.isInstanceOf[StartOfIteration]) { + return Iterator((tuple.left.get, Option.empty)) + } if (tuple.isLeft && tuple.left.get.isInstanceOf[EndOfIteration]) { return Iterator((tuple.left.get, Option.empty)) } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala index be2ba1ac559..026d6582bc5 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala @@ -18,7 +18,7 @@ class LoopEndOpDesc extends LogicalOp { workflowId, executionId, operatorIdentifier, - OpExecInitInfo((_, _, _) => new LoopEndOpExec()) + OpExecInitInfo((_, _, operatorConfig) => new LoopEndOpExec(operatorConfig.workerConfigs.head.workerId)) ) .withInputPorts(operatorInfo.inputPorts, inputPortToSchemaMapping) .withOutputPorts(operatorInfo.outputPorts, outputPortToSchemaMapping) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala index 18046e4dcbd..be40382a7ea 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala @@ -1,32 +1,49 @@ package edu.uci.ics.texera.workflow.operators.loop +import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{EndOfIteration, StartOfIteration} import edu.uci.ics.amber.engine.architecture.worker.PauseManager import edu.uci.ics.amber.engine.common.InputExhausted import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient +import edu.uci.ics.amber.engine.common.tuple.ITuple +import edu.uci.ics.amber.engine.common.virtualidentity.ActorVirtualIdentity +import edu.uci.ics.amber.engine.common.workflow.PortIdentity import edu.uci.ics.texera.workflow.common.operators.OperatorExecutor import edu.uci.ics.texera.workflow.common.tuple.Tuple import scala.collection.mutable -class LoopEndOpExec extends OperatorExecutor { +class LoopEndOpExec(val workerId: ActorVirtualIdentity) extends OperatorExecutor { var iteration = 0 - var buffer = new mutable.ArrayBuffer[Tuple] + var buffer = new mutable.ArrayBuffer[(ITuple, Option[PortIdentity])] override def open(): Unit = {} override def close(): Unit = {} - override def processTexeraTuple( - tuple: Either[Tuple, InputExhausted], - input: Int, - pauseManager: PauseManager, - asyncRPCClient: AsyncRPCClient - ): Iterator[Tuple] = { + override def processTuple( + tuple: Either[ITuple, InputExhausted], + input: Int, + pauseManager: PauseManager, + asyncRPCClient: AsyncRPCClient + ): Iterator[(ITuple, Option[PortIdentity])] = { tuple match { case Left(t) => - buffer.append(t) - Iterator() + t match { + case t: StartOfIteration => + Iterator((EndOfIteration(t.workerId, workerId), None)) + case t => + buffer.append((t, None)) + Iterator() + } + case Right(_) => buffer.iterator } } + + override def processTexeraTuple( + tuple: Either[Tuple, InputExhausted], + input: Int, + pauseManager: PauseManager, + asyncRPCClient: AsyncRPCClient + ): Iterator[Tuple] = ??? } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala index ef5beb71e9a..341dc1f046b 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala @@ -56,6 +56,6 @@ class LoopStartOpDesc extends LogicalOp { .add(schemas(1)) .build() } else { - schemas(1) + schemas(0) } } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala index c09cae37b2a..3124c9da1cc 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala @@ -1,5 +1,6 @@ package edu.uci.ics.texera.workflow.operators.loop +import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{EndOfIteration, FinalizeOperator, StartOfIteration} import edu.uci.ics.amber.engine.architecture.worker.PauseManager import edu.uci.ics.amber.engine.common.InputExhausted import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient @@ -18,35 +19,53 @@ class LoopStartOpExec( ) extends OperatorExecutor { var iteration = 0 var data = new mutable.ArrayBuffer[ITuple] + var buffer = new mutable.ArrayBuffer[ITuple] override def processTuple( tuple: Either[ITuple, InputExhausted], input: Int, pauseManager: PauseManager, asyncRPCClient: AsyncRPCClient ): Iterator[(ITuple, Option[PortIdentity])] = { - tuple match { - case Left(t) => input match { case 0 => - iteration += 1 - if (outputSchema.containsAttribute("Iteration")) { - data.iterator.map(dt => ( - Tuple.newBuilder(outputSchema).add(outputSchema.getAttribute("Iteration"), iteration-1) - .add(dt.asInstanceOf[Tuple]) - .add(t.asInstanceOf[Tuple]).build - , - None - )) - } else { - data.iterator.map(t => (t, None)) + tuple match { + case Left(t) => + t match { + case t: StartOfIteration => + if(iteration == buffer.length){ + return Iterator((FinalizeOperator(), None)) + } + val ret = Iterator((buffer(iteration), None), (StartOfIteration(workerId), None)) + iteration += 1 + ret + case t => + buffer.append(t) + Iterator.empty + + // iteration += 1 + // if (outputSchema.containsAttribute("Iteration")) { + // data.iterator.map(dt => ( + // Tuple.newBuilder(outputSchema).add(outputSchema.getAttribute("Iteration"), iteration-1) + // .add(dt.asInstanceOf[Tuple]) + // .add(t.asInstanceOf[Tuple]).build + // , + // None + // )) + // } else { + // data.iterator.map(t => (t, None)) + // } + } + case Right(_) => + iteration += 1 + Iterator((buffer(0), None), (StartOfIteration(workerId), None)) } case 1 => - data.append(t) + tuple match { + case Left(t) => data.append(t) + case Right(_) => + } Iterator.empty } - case Right(_) => Iterator.empty - //Iterator((EndOfIteration(workerId), None)) - } } override def open(): Unit = {} From c5145c62cd691eb2ca87a93c1998fb0bd3867963 Mon Sep 17 00:00:00 2001 From: linxinyuan Date: Fri, 23 Feb 2024 04:47:32 -0800 Subject: [PATCH 19/29] update --- .../architecture/worker/DataProcessor.scala | 22 +++--- .../operators/loop/LoopStartOpDesc.scala | 2 +- .../operators/loop/LoopStartOpExec.scala | 78 ++++++++++--------- 3 files changed, 54 insertions(+), 48 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index d3f5be010cc..a050c365668 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -16,10 +16,11 @@ import edu.uci.ics.amber.engine.architecture.logreplay.ReplayLogManager import edu.uci.ics.amber.engine.architecture.messaginglayer.{OutputManager, WorkerTimerService} import edu.uci.ics.amber.engine.architecture.scheduling.config.OperatorConfig import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{ - EndOfIteration, DPOutputIterator, + EndOfIteration, FinalizeOperator, - FinalizePort + FinalizePort, + StartOfIteration } import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.MainThreadDelegateMessage import edu.uci.ics.amber.engine.architecture.worker.promisehandlers.PauseHandler.PauseWorker @@ -67,7 +68,8 @@ object DataProcessor { case class FinalizeOperator() extends SpecialDataTuple case class StartOfIteration(workerId: ActorVirtualIdentity) extends SpecialDataTuple - case class EndOfIteration(startWorkerId: ActorVirtualIdentity, endWorkerId: ActorVirtualIdentity) extends SpecialDataTuple + case class EndOfIteration(startWorkerId: ActorVirtualIdentity, endWorkerId: ActorVirtualIdentity) + extends SpecialDataTuple class DPOutputIterator extends Iterator[(ITuple, Option[PortIdentity])] { val queue = new mutable.Queue[(ITuple, Option[PortIdentity])] @@ -154,9 +156,9 @@ class DataProcessor( } /** provide API for actor to get stats of this operator - * - * @return (input tuple count, output tuple count) - */ + * + * @return (input tuple count, output tuple count) + */ def collectStatistics(): WorkerStatistics = statisticsManager.getStatistics(stateManager.getCurrentState, operator) @@ -176,7 +178,7 @@ class DataProcessor( asyncRPCClient ) ) - if (tuple.isLeft) { + if (tuple.isLeft && !tuple.left.get.isInstanceOf[StartOfIteration]) { statisticsManager.increaseInputTupleCount() } } catch safely { @@ -227,7 +229,9 @@ class DataProcessor( case FinalizePort(portId, input) => asyncRPCClient.send(PortCompleted(portId, input), CONTROLLER) case _ => - statisticsManager.increaseOutputTupleCount() + if (!outputTuple.isInstanceOf[StartOfIteration]) { + statisticsManager.increaseOutputTupleCount() + } val outLinks = physicalOp.getOutputLinks(outputPortOpt) outLinks.foreach(link => outputManager.passTupleToDownstream(outputTuple, link)) } @@ -301,7 +305,7 @@ class DataProcessor( .foreach(outputPortId => outputIterator.appendSpecialTupleToEnd(FinalizePort(outputPortId, input = false)) ) - if(!operator.isInstanceOf[LoopStartOpExec]){ + if (!operator.isInstanceOf[LoopStartOpExec]) { outputIterator.appendSpecialTupleToEnd(FinalizeOperator()) } } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala index 341dc1f046b..ef5beb71e9a 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpDesc.scala @@ -56,6 +56,6 @@ class LoopStartOpDesc extends LogicalOp { .add(schemas(1)) .build() } else { - schemas(0) + schemas(1) } } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala index 3124c9da1cc..fd29bfdb2d7 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartOpExec.scala @@ -1,6 +1,10 @@ package edu.uci.ics.texera.workflow.operators.loop -import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{EndOfIteration, FinalizeOperator, StartOfIteration} +import edu.uci.ics.amber.engine.architecture.worker.DataProcessor.{ + EndOfIteration, + FinalizeOperator, + StartOfIteration +} import edu.uci.ics.amber.engine.architecture.worker.PauseManager import edu.uci.ics.amber.engine.common.InputExhausted import edu.uci.ics.amber.engine.common.rpc.AsyncRPCClient @@ -26,46 +30,44 @@ class LoopStartOpExec( pauseManager: PauseManager, asyncRPCClient: AsyncRPCClient ): Iterator[(ITuple, Option[PortIdentity])] = { - input match { - case 0 => - tuple match { - case Left(t) => - t match { - case t: StartOfIteration => - if(iteration == buffer.length){ - return Iterator((FinalizeOperator(), None)) - } - val ret = Iterator((buffer(iteration), None), (StartOfIteration(workerId), None)) - iteration += 1 - ret - case t => - buffer.append(t) - Iterator.empty + input match { + case 0 => + tuple match { + case Left(t) => + t match { + case t: StartOfIteration => + { + if (iteration == buffer.length) { + return Iterator((FinalizeOperator(), None)) + } + iteration += 1 + if (outputSchema.containsAttribute("Iteration")) { + data.iterator.map(dt => ( + Tuple.newBuilder(outputSchema).add(outputSchema.getAttribute("Iteration"), iteration-1) + .add(dt.asInstanceOf[Tuple]) + .add(buffer(iteration-1).asInstanceOf[Tuple]).build + , + None + )) + } else { + data.iterator.map(t => (t, None)) + } + } ++ Iterator((StartOfIteration(workerId), None)) - // iteration += 1 - // if (outputSchema.containsAttribute("Iteration")) { - // data.iterator.map(dt => ( - // Tuple.newBuilder(outputSchema).add(outputSchema.getAttribute("Iteration"), iteration-1) - // .add(dt.asInstanceOf[Tuple]) - // .add(t.asInstanceOf[Tuple]).build - // , - // None - // )) - // } else { - // data.iterator.map(t => (t, None)) - // } - } - case Right(_) => - iteration += 1 - Iterator((buffer(0), None), (StartOfIteration(workerId), None)) - } - case 1 => - tuple match { - case Left(t) => data.append(t) - case Right(_) => + case t => + buffer.append(t) + Iterator.empty } - Iterator.empty + case Right(_) => + Iterator((StartOfIteration(workerId), None)) + } + case 1 => + tuple match { + case Left(t) => data.append(t) + case Right(_) => } + Iterator.empty + } } override def open(): Unit = {} From 8ac537b312da916918722cc82b07f84bc635535b Mon Sep 17 00:00:00 2001 From: linxinyuan Date: Wed, 28 Feb 2024 01:06:45 -0800 Subject: [PATCH 20/29] update --- .../ics/texera/workflow/operators/loop/LoopStartV2OpExec.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartV2OpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartV2OpExec.scala index ff1d9616e2a..653c76fe5c9 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartV2OpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopStartV2OpExec.scala @@ -46,7 +46,6 @@ class LoopStartV2OpExec( Iterator.empty } case Right(_) => Iterator.empty - //Iterator((EndOfIteration(workerId), None)) } } From de07aebae3a400ca540530bc21439a0ab5233c95 Mon Sep 17 00:00:00 2001 From: linxinyuan Date: Wed, 28 Feb 2024 03:37:03 -0800 Subject: [PATCH 21/29] update --- .../src/main/python/core/models/operator.py | 33 +++++++++++++++++ .../src/main/python/pytexera/__init__.py | 2 ++ .../main/python/pytexera/udf/udf_operator.py | 35 +++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/core/amber/src/main/python/core/models/operator.py b/core/amber/src/main/python/core/models/operator.py index e44dd599739..320428d15ec 100644 --- a/core/amber/src/main/python/core/models/operator.py +++ b/core/amber/src/main/python/core/models/operator.py @@ -226,6 +226,39 @@ def process_table(self, table: Table, port: int) -> Iterator[Optional[TableLike] yield +class IterationTableOperator(TupleOperatorV2): + """ + Base class for table-oriented operators. A concrete implementation must + be provided upon using. + """ + + def __init__(self): + super().__init__() + self.__internal_is_source: bool = False + self.__table_data: Mapping[int, Mapping[int, List[Tuple]]] = defaultdict(lambda: defaultdict(list)) + + @overrides.final + def process_tuple(self, tuple_: Tuple, port: int) -> Iterator[Optional[TupleLike]]: + self.__table_data[port][tuple_["Iteration"]].append(tuple_) + yield + + def on_finish(self, port: int) -> Iterator[Optional[TableLike]]: + for table in self.__table_data[port].values(): + yield from self.process_table(Table(table), port) + + @abstractmethod + def process_table(self, table: Table, port: int) -> Iterator[Optional[TableLike]]: + """ + Process an input Table from the given link. The Table is represented as a + pandas.DataFrame. + + :param table: Table, a table to be processed. + :param port: int, input port index of the current Tuple. + :return: Iterator[Optional[TableLike]], producing one TableLike object at a + time, or None. + """ + yield + @deprecated(reason="Use TupleOperatorV2 instead") class TupleOperator(Operator): """ diff --git a/core/amber/src/main/python/pytexera/__init__.py b/core/amber/src/main/python/pytexera/__init__.py index 2277b744a58..f76f04ce24c 100644 --- a/core/amber/src/main/python/pytexera/__init__.py +++ b/core/amber/src/main/python/pytexera/__init__.py @@ -7,6 +7,7 @@ UDFOperator, UDFOperatorV2, UDFTableOperator, + UDFIterationTableOperator, UDFBatchOperator, UDFSourceOperator, ) @@ -22,6 +23,7 @@ "Batch", "BatchLike", "UDFTableOperator", + "UDFIterationTableOperator", "UDFBatchOperator", "UDFSourceOperator", # export external tools to be used diff --git a/core/amber/src/main/python/pytexera/udf/udf_operator.py b/core/amber/src/main/python/pytexera/udf/udf_operator.py index fcfa2abd166..6c4ddf94d3d 100644 --- a/core/amber/src/main/python/pytexera/udf/udf_operator.py +++ b/core/amber/src/main/python/pytexera/udf/udf_operator.py @@ -1,6 +1,8 @@ from abc import abstractmethod from typing import Iterator, Optional, Union from deprecated import deprecated + +from core.models.operator import IterationTableOperator from pyamber import * @@ -138,6 +140,39 @@ def close(self) -> None: pass +class UDFIterationTableOperator(IterationTableOperator): + """ + Base class for table-oriented user-defined operators. A concrete implementation must + be provided upon using. + """ + + def open(self) -> None: + """ + Open a context of the operator. Usually can be used for loading/initiating some + resources, such as a file, a model, or an API client. + """ + pass + + @abstractmethod + def process_table(self, table: Table, port: int) -> Iterator[Optional[TableLike]]: + """ + Process an input Table from the given link. The Table is represented as + pandas.DataFrame. + + :param table: Table, a table to be processed. + :param port: int, input index of the current Table. + :return: Iterator[Optional[TableLike]], producing one TableLike object at a + time, or None. + """ + yield + + def close(self) -> None: + """ + Close the context of the operator. + """ + pass + + class UDFBatchOperator(BatchOperator): """ Base class for batch-oriented user-defined operators. A concrete implementation must From a692923e70f3fe88a48984397e5a89a327960edc Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Wed, 28 Feb 2024 16:14:51 -0800 Subject: [PATCH 22/29] fix it --- .../workspace/service/workflow-graph/model/workflow-graph.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/new-gui/src/app/workspace/service/workflow-graph/model/workflow-graph.ts b/core/new-gui/src/app/workspace/service/workflow-graph/model/workflow-graph.ts index f42afcc0962..8fcb54c4797 100644 --- a/core/new-gui/src/app/workspace/service/workflow-graph/model/workflow-graph.ts +++ b/core/new-gui/src/app/workspace/service/workflow-graph/model/workflow-graph.ts @@ -817,7 +817,7 @@ export class WorkflowGraph { // ) as YType; // set the new copy back to the operator ID map // TODO: we temporarily disable this due to Yjs update causing issues in Formly. - this.getSharedOperatorType(operatorID).set("operatorProperties", newProperty); + this.getSharedOperatorType(operatorID).set("operatorProperties", createYTypeFromObject(newProperty)); // updateYTypeFromObject(previousProperty, newProperty); } From 478f6b427ca68b25748c4786baea046a26b49aa2 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Wed, 28 Feb 2024 16:29:41 -0800 Subject: [PATCH 23/29] update --- .../src/main/python/core/models/operator.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/core/amber/src/main/python/core/models/operator.py b/core/amber/src/main/python/core/models/operator.py index 320428d15ec..3db905761a7 100644 --- a/core/amber/src/main/python/core/models/operator.py +++ b/core/amber/src/main/python/core/models/operator.py @@ -202,15 +202,23 @@ def __init__(self): super().__init__() self.__internal_is_source: bool = False self.__table_data: Mapping[int, List[Tuple]] = defaultdict(list) + self.__it_table_data: Mapping[int, Mapping[int, List[Tuple]]] = defaultdict(lambda: defaultdict(list)) + self.__internal_is_it: bool = False @overrides.final def process_tuple(self, tuple_: Tuple, port: int) -> Iterator[Optional[TupleLike]]: + if "Iteration" in tuple_: + self.__internal_is_it = True + self.__it_table_data[port][tuple_["Iteration"]].append(tuple_) self.__table_data[port].append(tuple_) yield def on_finish(self, port: int) -> Iterator[Optional[TableLike]]: - table = Table(self.__table_data[port]) - yield from self.process_table(table, port) + if self.__internal_is_it: + for table in self.__it_table_data[port].values(): + yield from self.process_table(Table(table), port) + else: + yield from self.process_table(Table(self.__table_data[port]), port) @abstractmethod def process_table(self, table: Table, port: int) -> Iterator[Optional[TableLike]]: @@ -235,11 +243,11 @@ class IterationTableOperator(TupleOperatorV2): def __init__(self): super().__init__() self.__internal_is_source: bool = False - self.__table_data: Mapping[int, Mapping[int, List[Tuple]]] = defaultdict(lambda: defaultdict(list)) + @overrides.final def process_tuple(self, tuple_: Tuple, port: int) -> Iterator[Optional[TupleLike]]: - self.__table_data[port][tuple_["Iteration"]].append(tuple_) + yield def on_finish(self, port: int) -> Iterator[Optional[TableLike]]: From 498f46f97a005841a2a6bb5a46b1b00f642aaf5f Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Wed, 28 Feb 2024 16:34:42 -0800 Subject: [PATCH 24/29] update --- .../src/main/python/core/models/operator.py | 41 ++----------------- .../src/main/python/pytexera/__init__.py | 2 - .../main/python/pytexera/udf/udf_operator.py | 34 --------------- 3 files changed, 4 insertions(+), 73 deletions(-) diff --git a/core/amber/src/main/python/core/models/operator.py b/core/amber/src/main/python/core/models/operator.py index 3db905761a7..119813a837e 100644 --- a/core/amber/src/main/python/core/models/operator.py +++ b/core/amber/src/main/python/core/models/operator.py @@ -45,7 +45,7 @@ def output_schema(self) -> Schema: @output_schema.setter @overrides.final def output_schema( - self, raw_output_schema: Union[Schema, Mapping[str, str]] + self, raw_output_schema: Union[Schema, Mapping[str, str]] ) -> None: self.__internal_output_schema = ( raw_output_schema @@ -147,8 +147,8 @@ def _validate_batch_size(value): def process_tuple(self, tuple_: Tuple, port: int) -> Iterator[Optional[TupleLike]]: self.__batch_data[port].append(tuple_) if ( - self.BATCH_SIZE is not None - and len(self.__batch_data[port]) >= self.BATCH_SIZE + self.BATCH_SIZE is not None + and len(self.__batch_data[port]) >= self.BATCH_SIZE ): yield from self._process_batch(port) @@ -234,39 +234,6 @@ def process_table(self, table: Table, port: int) -> Iterator[Optional[TableLike] yield -class IterationTableOperator(TupleOperatorV2): - """ - Base class for table-oriented operators. A concrete implementation must - be provided upon using. - """ - - def __init__(self): - super().__init__() - self.__internal_is_source: bool = False - - - @overrides.final - def process_tuple(self, tuple_: Tuple, port: int) -> Iterator[Optional[TupleLike]]: - - yield - - def on_finish(self, port: int) -> Iterator[Optional[TableLike]]: - for table in self.__table_data[port].values(): - yield from self.process_table(Table(table), port) - - @abstractmethod - def process_table(self, table: Table, port: int) -> Iterator[Optional[TableLike]]: - """ - Process an input Table from the given link. The Table is represented as a - pandas.DataFrame. - - :param table: Table, a table to be processed. - :param port: int, input port index of the current Tuple. - :return: Iterator[Optional[TableLike]], producing one TableLike object at a - time, or None. - """ - yield - @deprecated(reason="Use TupleOperatorV2 instead") class TupleOperator(Operator): """ @@ -276,7 +243,7 @@ class TupleOperator(Operator): @abstractmethod def process_tuple( - self, tuple_: Union[Tuple, InputExhausted], input_: int + self, tuple_: Union[Tuple, InputExhausted], input_: int ) -> Iterator[Optional[TupleLike]]: """ Process an input Tuple from the given link. diff --git a/core/amber/src/main/python/pytexera/__init__.py b/core/amber/src/main/python/pytexera/__init__.py index f76f04ce24c..2277b744a58 100644 --- a/core/amber/src/main/python/pytexera/__init__.py +++ b/core/amber/src/main/python/pytexera/__init__.py @@ -7,7 +7,6 @@ UDFOperator, UDFOperatorV2, UDFTableOperator, - UDFIterationTableOperator, UDFBatchOperator, UDFSourceOperator, ) @@ -23,7 +22,6 @@ "Batch", "BatchLike", "UDFTableOperator", - "UDFIterationTableOperator", "UDFBatchOperator", "UDFSourceOperator", # export external tools to be used diff --git a/core/amber/src/main/python/pytexera/udf/udf_operator.py b/core/amber/src/main/python/pytexera/udf/udf_operator.py index 6c4ddf94d3d..1b5836d42f0 100644 --- a/core/amber/src/main/python/pytexera/udf/udf_operator.py +++ b/core/amber/src/main/python/pytexera/udf/udf_operator.py @@ -2,7 +2,6 @@ from typing import Iterator, Optional, Union from deprecated import deprecated -from core.models.operator import IterationTableOperator from pyamber import * @@ -140,39 +139,6 @@ def close(self) -> None: pass -class UDFIterationTableOperator(IterationTableOperator): - """ - Base class for table-oriented user-defined operators. A concrete implementation must - be provided upon using. - """ - - def open(self) -> None: - """ - Open a context of the operator. Usually can be used for loading/initiating some - resources, such as a file, a model, or an API client. - """ - pass - - @abstractmethod - def process_table(self, table: Table, port: int) -> Iterator[Optional[TableLike]]: - """ - Process an input Table from the given link. The Table is represented as - pandas.DataFrame. - - :param table: Table, a table to be processed. - :param port: int, input index of the current Table. - :return: Iterator[Optional[TableLike]], producing one TableLike object at a - time, or None. - """ - yield - - def close(self) -> None: - """ - Close the context of the operator. - """ - pass - - class UDFBatchOperator(BatchOperator): """ Base class for batch-oriented user-defined operators. A concrete implementation must From 49e27bc62c8f196e6dc1b56dc40307b48f97c5aa Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Tue, 5 Mar 2024 15:53:07 -0800 Subject: [PATCH 25/29] Remove Iteration at loop end --- .../uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala index 026d6582bc5..d7903b51070 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpDesc.scala @@ -35,6 +35,5 @@ class LoopEndOpDesc extends LogicalOp { supportReconfiguration = true ) - override def getOutputSchema(schemas: Array[Schema]): Schema = schemas(0) - + override def getOutputSchema(schemas: Array[Schema]): Schema = new Schema.Builder(schemas(0)).removeIfExists("Iteration").build() } From 656be0ef25381dbc47e34b2f5aac9a753b00d513 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Tue, 12 Mar 2024 15:50:45 -0700 Subject: [PATCH 26/29] fix --- .../workflow/operators/loop/LoopEndOpExec.scala | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala index be40382a7ea..be1a8754ea5 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala @@ -9,6 +9,7 @@ import edu.uci.ics.amber.engine.common.virtualidentity.ActorVirtualIdentity import edu.uci.ics.amber.engine.common.workflow.PortIdentity import edu.uci.ics.texera.workflow.common.operators.OperatorExecutor import edu.uci.ics.texera.workflow.common.tuple.Tuple +import edu.uci.ics.texera.workflow.common.tuple.schema.Schema import scala.collection.mutable @@ -30,9 +31,18 @@ class LoopEndOpExec(val workerId: ActorVirtualIdentity) extends OperatorExecutor case Left(t) => t match { case t: StartOfIteration => + Iterator((EndOfIteration(t.workerId, workerId), None)) case t => - buffer.append((t, None)) + val schema = t.asInstanceOf[Tuple].getSchema + if (schema.containsAttribute("Iteration")){ + val s = new Schema.Builder(schema).removeIfExists("Iteration").build() + buffer.append((Tuple.newBuilder(s).addSequentially( + s.getAttributesScala.map(attr => t.asInstanceOf[Tuple].getField[AnyRef](attr.getName)).toArray).build(), None)) + + } else { + buffer.append((t, None)) + } Iterator() } From f36bcb401cab4882b22b9ae08c732f1894a3fc6c Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Tue, 12 Mar 2024 16:11:05 -0700 Subject: [PATCH 27/29] fix --- .../uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala index be1a8754ea5..1ec6cdf1639 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/loop/LoopEndOpExec.scala @@ -38,8 +38,7 @@ class LoopEndOpExec(val workerId: ActorVirtualIdentity) extends OperatorExecutor if (schema.containsAttribute("Iteration")){ val s = new Schema.Builder(schema).removeIfExists("Iteration").build() buffer.append((Tuple.newBuilder(s).addSequentially( - s.getAttributesScala.map(attr => t.asInstanceOf[Tuple].getField[AnyRef](attr.getName)).toArray).build(), None)) - + s.getAttributesScala.map(attr => t.asInstanceOf[Tuple].getField[Object](attr.getName)).toArray).build(), None)) } else { buffer.append((t, None)) } From 82abb24a7a1f08553559754271dce1f196340f59 Mon Sep 17 00:00:00 2001 From: linxinyuan Date: Sat, 15 Jun 2024 18:51:09 -0700 Subject: [PATCH 28/29] if statement --- .../workflow/common/operators/LogicalOp.scala | 2 ++ .../ifstatement/IfStatementOpDesc.scala | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/ifstatement/IfStatementOpDesc.scala diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala index d0fbe10cfbc..e248dd9eb4e 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/common/operators/LogicalOp.scala @@ -19,6 +19,7 @@ import edu.uci.ics.texera.workflow.operators.distinct.DistinctOpDesc import edu.uci.ics.texera.workflow.operators.download.BulkDownloaderOpDesc import edu.uci.ics.texera.workflow.operators.filter.SpecializedFilterOpDesc import edu.uci.ics.texera.workflow.operators.hashJoin.HashJoinOpDesc +import edu.uci.ics.texera.workflow.operators.ifstatement.IfStatementOpDesc import edu.uci.ics.texera.workflow.operators.intersect.IntersectOpDesc import edu.uci.ics.texera.workflow.operators.intervalJoin.IntervalJoinOpDesc import edu.uci.ics.texera.workflow.operators.keywordSearch.KeywordSearchOpDesc @@ -128,6 +129,7 @@ trait StateTransferFunc new Type(value = classOf[LoopStartV2OpDesc], name = "LoopStartV2"), new Type(value = classOf[GeneratorOpDesc], name = "Generator"), new Type(value = classOf[LoopEndOpDesc], name = "LoopEnd"), + new Type(value = classOf[IfStatementOpDesc], name = "IfStatement"), new Type(value = classOf[RandomKSamplingOpDesc], name = "RandomKSampling"), new Type(value = classOf[ReservoirSamplingOpDesc], name = "ReservoirSampling"), new Type(value = classOf[HashJoinOpDesc[String]], name = "HashJoin"), diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/ifstatement/IfStatementOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/ifstatement/IfStatementOpDesc.scala new file mode 100644 index 00000000000..bf7655f4335 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/ifstatement/IfStatementOpDesc.scala @@ -0,0 +1,24 @@ +package edu.uci.ics.texera.workflow.operators.ifstatement + +import com.fasterxml.jackson.annotation.JsonProperty +import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle +import edu.uci.ics.amber.engine.common.workflow.{InputPort, OutputPort, PortIdentity} +import edu.uci.ics.texera.workflow.common.metadata.{OperatorGroupConstants, OperatorInfo} +import edu.uci.ics.texera.workflow.common.operators.LogicalOp +import edu.uci.ics.texera.workflow.common.tuple.schema.Schema +class IfStatementOpDesc extends LogicalOp { + @JsonProperty(required = true) + @JsonSchemaTitle("Expression") + var exp: String = _ + + override def operatorInfo: OperatorInfo = + OperatorInfo( + "If Statement", + "If Statement", + OperatorGroupConstants.UTILITY_GROUP, + inputPorts = List(InputPort(PortIdentity(0),"Condition"), InputPort(PortIdentity(1),"Data")), + outputPorts = List(OutputPort(PortIdentity(0),"True"), OutputPort(PortIdentity(1),"False")) + ) + + override def getOutputSchema(schemas: Array[Schema]): Schema = ??? +} From fc70e5f2442918dada2a833361f8f1ae81606419 Mon Sep 17 00:00:00 2001 From: linxinyuan Date: Mon, 24 Jun 2024 18:50:39 -0700 Subject: [PATCH 29/29] if statement --- .../src/assets/operator_images/IfStatement.png | Bin 0 -> 7020 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 core/new-gui/src/assets/operator_images/IfStatement.png diff --git a/core/new-gui/src/assets/operator_images/IfStatement.png b/core/new-gui/src/assets/operator_images/IfStatement.png new file mode 100644 index 0000000000000000000000000000000000000000..5808af116b04012394690205c0253b4a42a23546 GIT binary patch literal 7020 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