def __init__(
        self,
        inference_config=None,
        serving_config=None,
        steps=None,
        extra_start_args="-Xmx8g",
        config_path="config.json",
        jar_path=None,
        pid_file_path="konduit-serving.pid",
        start_timeout=120,
    ):
        """Konduit Server

        Start and stop a server from a given inference configuration. Alternatively,
        you can provide a serving configuration and a list of steps, instead of an
        inference config.
        Extra arguments with JVM options can be passed.

        Example:

        >>> import konduit
        >>> server = konduit.Server()

        :param inference_config: InferenceConfiguration instance
        :param serving_config: ServingConfig instance
        :param steps: list (or single) PipelineSteps
        :param extra_start_args: list of string arguments to start the process with
        :param config_path: path to write the config object to (as json)
        :param jar_path: path to the konduit uberjar
        """
        if jar_path is None:
            jar_path = os.getenv("KONDUIT_JAR_PATH",
                                 os.path.join(KONDUIT_DIR, "konduit.jar"))

        if inference_config:
            self.config = inference_config
        elif serving_config and steps:
            if isinstance(steps, PipelineStep):
                steps = [steps]
            self.config = InferenceConfiguration(steps=steps,
                                                 serving_config=serving_config)
        else:
            self.config = InferenceConfiguration()
        self.config_path = config_path
        self.jar_path = jar_path
        self.pid_file_path = pid_file_path
        self.start_timeout = start_timeout
        self.process = None
        if extra_start_args is None:
            extra_start_args = []

        # Handle singular element case
        if type(extra_start_args) is not list:
            self.extra_start_args = [extra_start_args]
        else:
            self.extra_start_args = extra_start_args
 def __init__(self,
              config=InferenceConfiguration(),
              extra_start_args=[],
              config_path='config.json',
              jar_path='konduit.jar'):
     self.config = config
     self.config_path = config_path
     self.jar_path = jar_path
     self.process = None
     # Handle singular element case
     if extra_start_args is not None and type(extra_start_args) is not list:
         self.extra_start_args = [extra_start_args]
     else:
         self.extra_start_args = extra_start_args
Exemple #3
0
class Server(object):
    def __init__(self,
                 inference_config=None,
                 serving_config=None,
                 steps=None,
                 extra_start_args="-Xmx8g",
                 extra_jar_args=None,
                 config_path="config.yaml",
                 jar_path=None,
                 pid_file_path="konduit-serving.pid"):
        """Konduit Server

        Start and stop a server from a given inference configuration. Alternatively,
        you can provide a serving configuration and a list of steps, instead of an
        inference config.
        Extra arguments with JVM options can be passed.

        Example:

        >>> import konduit
        >>> server = konduit.Server()

        :param inference_config: InferenceConfiguration instance
        :param serving_config: ServingConfig instance
        :param steps: list (or single) PipelineSteps
        :param extra_start_args: list of string arguments to start the process with
        :param extra_jar_args: extra jar files to add to the classpath
        :param config_path: path to write the config object to (as json)
        :param jar_path: path to the konduit uberjar
        """
        self.port = -1
        if jar_path is None:
            jar_path = os.getenv("KONDUIT_JAR_PATH",
                                 os.path.join(KONDUIT_JAR_DIR, "konduit.jar"))

        if inference_config:
            self.config = inference_config
        elif serving_config and steps:
            if isinstance(steps, PipelineStep):
                steps = [steps]
            self.config = InferenceConfiguration(steps=steps,
                                                 serving_config=serving_config)
        else:
            self.config = InferenceConfiguration()
        self.config_path = config_path
        self.jar_path = jar_path
        self.pid_file_path = pid_file_path
        self.process = None
        if extra_start_args is None:
            extra_start_args = []

        if extra_jar_args is None:
            extra_jar_args = []

        # Handle singular element case
        if type(extra_start_args) is not list:
            self.extra_start_args = [extra_start_args]
        else:
            self.extra_start_args = extra_start_args

        if type(extra_jar_args) is not list:
            self.extra_jar_args = [extra_jar_args]
        else:
            self.extra_jar_args = extra_jar_args

        self.server_id = uuid.uuid4().hex[:8]

    def get_client(self,
                   input_data_format=None,
                   prediction_type=None,
                   output_data_format=None):
        """Get a Konduit Client instance from this Server instance.
        :param output_data_format: optional, same as in Client signature
        :return: konduit.Client
        """
        serving_config = self.config._get_serving_config()
        steps = self.config._get_steps()
        input_names = []
        output_names = []
        for step in steps:
            input_names += step._get_input_names()
            output_names += step._get_output_names()

        port = self.port
        host = serving_config._get_listen_host()
        if not host:
            host = "http://localhost"
        else:
            if not host.startswith("http://"):
                host = "http://" + host

        if not input_data_format:
            input_data_format = "NUMPY"

        if not prediction_type:
            prediction_type = "RAW"

        if not output_data_format:
            output_data_format = serving_config._get_output_data_format()

        return Client(
            host=host,
            port=port,
            input_data_format=input_data_format,
            output_data_format=output_data_format,
            prediction_type=prediction_type,
            input_names=input_names,
            output_names=output_names,
        )

    def start(self, server_id=None):
        """Start the Konduit server

        :param server_id the server will be started with this id.
        """

        if not server_id:
            server_id = self.server_id
        else:
            self.server_id = server_id

        json_config = config_to_dict_with_type(self.config)
        with open(self.config_path, "w") as f:
            abs_path = os.path.abspath(self.config_path)
            logging.info("Wrote config.yaml to path " + abs_path)
            json.dump(json_config, f)

        args = self._process_extra_args(abs_path, server_id)
        process = subprocess.Popen(args=args, stdout=subprocess.PIPE)
        self.process = process

        # Check if the server is up or not.
        request_timeout = 5

        started = False

        print("Starting server")

        port = -1
        while True:
            output = process.stdout.readline()
            if output == b'' and process.poll() is not None:
                break
            else:
                print(output.decode("utf-8"), end="")
                if b'Inference server started on port' in output:
                    m = re.search('port (.+?) with', str(output))
                    if m:
                        port = int(m.group(1))
                        self.port = port
                        break

        if port != -1:
            try:
                r = requests.get(
                    "http://localhost:{}/healthcheck".format(port),
                    timeout=request_timeout,
                )
                if r.status_code == 204:
                    started = True
            except Exception as ex:
                logging.debug("Server health checks failed...\n{}".format(
                    str(ex)))

        process = process.poll()

        if started:
            print(
                "\n\nServer has started successfully on port {}".format(port))
        else:
            print("\n\nThe server wasn't able to start.")
            self.stop()

        return process, port, started

    def stop(self, server_id=None):
        """Stop the server"""
        if not server_id:
            server_id = self.server_id
        else:
            self.server_id = server_id

        command = self.get_command("stop " + server_id)
        logging.info("Running with args\n" + " ".join(command))
        subprocess.call(command, shell=sys.platform.startswith("win"))

    def _process_extra_args(self, absolute_path, server_id):
        """Process submitted extra arguments list.

        :param absolute_path: absolute path of the configuration file
        :return: concatenated string arguments
        """
        args = self.get_command("serve -c " + absolute_path)

        if server_id:
            args.append("-id")
            args.append(server_id)

        logging.info("Running with args\n" + " ".join(args))
        return args

    def get_command(self, command):
        classpath = [self.jar_path]
        classpath.extend(self.extra_jar_args)

        args = ["java"]
        # Pass extra jvm arguments such as memory.
        if self.extra_start_args:
            args.extend(self.extra_start_args)
        args.append("-cp")
        args.append(os.pathsep.join(classpath))
        args.append("ai.konduit.serving.launcher.KonduitServingLauncher")
        if command:
            args.extend(command.strip().split(" "))
        return args
Exemple #4
0
class Server(object):
    def __init__(
        self,
        inference_config=None,
        serving_config=None,
        steps=None,
        extra_start_args="-Xmx8g",
        extra_jar_args=None,
        config_path="config.json",
        jar_path=None,
        pid_file_path="konduit-serving.pid",
        start_timeout=120,
    ):
        """Konduit Server

        Start and stop a server from a given inference configuration. Alternatively,
        you can provide a serving configuration and a list of steps, instead of an
        inference config.
        Extra arguments with JVM options can be passed.

        Example:

        >>> import konduit
        >>> server = konduit.Server()

        :param inference_config: InferenceConfiguration instance
        :param serving_config: ServingConfig instance
        :param steps: list (or single) PipelineSteps
        :param extra_start_args: list of string arguments to start the process with
        :param extra_jar_args: extra jar files to add to the classpath
        :param config_path: path to write the config object to (as json)
        :param jar_path: path to the konduit uberjar
        """
        if jar_path is None:
            jar_path = os.getenv("KONDUIT_JAR_PATH",
                                 os.path.join(KONDUIT_DIR, "konduit.jar"))

        if inference_config:
            self.config = inference_config
        elif serving_config and steps:
            if isinstance(steps, PipelineStep):
                steps = [steps]
            self.config = InferenceConfiguration(steps=steps,
                                                 serving_config=serving_config)
        else:
            self.config = InferenceConfiguration()
        self.config_path = config_path
        self.jar_path = jar_path
        self.pid_file_path = pid_file_path
        self.start_timeout = start_timeout
        self.process = None
        if extra_start_args is None:
            extra_start_args = []

        if extra_jar_args is None:
            extra_jar_args = []

        # Handle singular element case
        if type(extra_start_args) is not list:
            self.extra_start_args = [extra_start_args]
        else:
            self.extra_start_args = extra_start_args

        if type(extra_jar_args) is not list:
            self.extra_jar_args = [extra_jar_args]
        else:
            self.extra_jar_args = extra_jar_args

    def get_client(self,
                   input_data_format=None,
                   prediction_type=None,
                   output_data_format=None):
        """Get a Konduit Client instance from this Server instance.
        :param output_data_format: optional, same as in Client signature
        :return: konduit.Client
        """
        serving_config = self.config._get_serving_config()
        steps = self.config._get_steps()
        input_names = []
        output_names = []
        for step in steps:
            input_names += step._get_input_names()
            output_names += step._get_output_names()

        port = serving_config._get_http_port()
        host = serving_config._get_listen_host()
        if not host.startswith("http://"):
            host = "http://" + host

        if not input_data_format:
            input_data_format = "NUMPY"

        if not prediction_type:
            prediction_type = "RAW"

        if not output_data_format:
            output_data_format = serving_config._get_output_data_format()

        return Client(
            host=host,
            port=port,
            input_data_format=input_data_format,
            output_data_format=output_data_format,
            prediction_type=prediction_type,
            input_names=input_names,
            output_names=output_names,
        )

    def start(self, kill_existing_server=True):
        """Start the Konduit server

        :param kill_existing_server: whether to kill any previously started server if it wasn't stop.
        """
        if kill_existing_server:
            if os.path.exists(self.pid_file_path):
                with open(self.pid_file_path, "rb") as pid_file:
                    pid = int(pid_file.readline().strip())
                    try:
                        stop_server_by_pid(pid)
                    except OSError:
                        logging.debug(
                            "Attempt to kill existing process by pid: '{}' failed. The process might not "
                            "exist. ".format(pid))

                os.remove(self.pid_file_path)

        json_config = config_to_dict_with_type(self.config)
        with open(self.config_path, "w") as f:
            abs_path = os.path.abspath(self.config_path)
            logging.info("Wrote config.json to path " + abs_path)
            json.dump(json_config, f)

        args = self._process_extra_args(abs_path)
        process = subprocess.Popen(args=args)
        self.process = process

        # Check if the server is up or not.
        request_timeout = 5
        tries = int(self.start_timeout / request_timeout + 1)

        started = False

        print("Starting server", end="")

        for i in range(tries):
            start_time = time.time()

            # This url returns status 204 with no content when the server is up.
            try:
                """
                This would look like the following on success:
                ------
                Starting server...
                
                Server has started successfully.
                ------ 
                
                and like this on failure
                ------
                Starting server...
                
                The server wasn't able to start.
                ------ 
                """
                print(".", end="")
                logging.debug(
                    "Checking server integrity. Tries: {} of {}".format(
                        i + 1, tries))

                r = requests.get(
                    "http://localhost:{}/healthcheck".format(
                        self.config.serving_config.http_port),
                    timeout=request_timeout,
                )
                if r.status_code == 204:
                    started = True
                    break
            except Exception as ex:
                logging.debug("{}\nChecking server integrity again...".format(
                    str(ex)))

            time_taken = time.time() - start_time

            if time_taken < request_timeout:
                time.sleep(
                    request_timeout - time_taken
                )  # Making sure the loop takes exactly "request_timeout" seconds

        if started:
            print("\n\nServer has started successfully.")
        else:
            print("\n\nThe server wasn't able to start.")
            self.stop()

        return process

    def stop(self):
        """Stop the server"""
        if self.process is None:
            if os.path.exists(self.config_path):
                os.remove(self.config_path)
            raise Exception("Server is not started!")
        else:
            if os.path.exists(self.config_path):
                os.remove(self.config_path)
            self.process.kill()

    def _process_extra_args(self, absolute_path):
        """Process submitted extra arguments list.

        :param absolute_path: absolute path of the configuration file
        :return: concatenated string arguments
        """
        classpath = [self.jar_path]
        classpath.extend(self.extra_jar_args)

        args = ["java"]
        # Pass extra jvm arguments such as memory.
        if self.extra_start_args:
            args.extend(self.extra_start_args)
        args.append("-cp")
        args.append(os.pathsep.join(classpath))
        args.append("ai.konduit.serving.configprovider.KonduitServingMain")
        args.append("--pidFile")
        args.append(os.path.abspath(self.pid_file_path))
        args.append("--configPath")
        args.append(absolute_path)
        args.append("--verticleClassName")
        args.append("ai.konduit.serving.verticles.inference.InferenceVerticle")
        logging.info("Running with args\n" + " ".join(args))
        return args