def __init__(self, table: Structures.Table, fields: Dict[str, Any], tags: Dict[str, Any] = None,
                 time_stamp: Union[int, str] = None):
        if(not table):
            raise ValueError("need table to create query")
        if(not fields):
            raise ValueError("need at least one value to create query")
        if(tags is None):
            tags = {}
        if(time_stamp is None):
            time_stamp = SppUtils.get_actual_time_sec()

        # Keyword is always Insert since insert Statement
        self.__keyword = Keyword.INSERT
        self.__table = table

        self.__time_stamp = SppUtils.epoch_time_to_seconds(time_stamp)
        fields = self.format_fields(fields)

        # make sure you have some fields if they are not privided
        if(not list(filter(lambda field_tup: field_tup[1] is not None, fields.items()))):
            # need default def to be able to do anything
            if(not table.fields):
                raise ValueError("fields after formatting empty, need at least one value!")
            # only works for strings, any other addition would corrupt the data
            for (key, datatype) in table.fields.items():
                if(datatype is Structures.Datatype.STRING):
                    fields[key] = '\"autofilled\"'
                    break
            # test again, improvement possible here
            if(not list(filter(lambda field_tup: field_tup[1] is not None, fields.items()))):
                raise ValueError("fields after formatting empty, need at least one value!")

        self.__fields: Dict[str, Union[int, float, str, bool]] = fields
        self.__tags: Dict[str, str] = self.format_tags(tags)
    def __insert_metrics_to_buffer(self,
                                   keyword: Keyword,
                                   tables_count: Dict[Table, int],
                                   duration_s: float,
                                   batch_size: int = 1) -> None:
        """Generates statistics per send Batch, total duration is split by item per table.

        Arguments:
            keyword {Keyword} -- Kind of query.
            tables_count {dict} -- Tables send in this batch, key is table, value is count of items.
            duration_s {float} -- Time needed to send the batch in seconds.

        Keyword Arguments:
            batch_size {int} -- Ammount of queries sent in one batch sent at once. (default: {1})

        Raises:
            ValueError: Any arg does not match the defined parameters or value is unsupported
        """
        # Arg checks
        if (list(
                filter(lambda arg: arg is None,
                       [keyword, tables_count, duration_s, batch_size]))):
            raise ValueError("any metric arg is None. This is not supported")
        if (not isinstance(keyword, Keyword)):
            raise ValueError("need the keyword to be a instance of keyword.")
        if (not tables_count or not isinstance(tables_count, dict)):
            raise ValueError(
                "need at least one entry of a table in tables_count.")
        if (duration_s <= 0):
            raise ValueError(
                "only positive values are supported for duration. Must be not 0"
            )
        if (batch_size < 1):
            raise ValueError(
                "only positive values are supported for batch_size. Must be not 0"
            )

        # get shared record time to be saved on
        querys = []

        # save metrics for each involved table individually
        for (table, item_count) in tables_count.items():
            querys.append(
                InsertQuery(
                    table=self.__metrics_table,
                    fields={
                        # Calculating relative duration for this part of whole query
                        'duration_ms':
                        duration_s * 1000 * (max(item_count, 1) / batch_size),
                        'item_count':
                        item_count,
                    },
                    tags={
                        'keyword': keyword,
                        'tableName': table.name,
                    },
                    time_stamp=SppUtils.get_actual_time_sec()))
        self.__insert_buffer[self.__metrics_table] = self.__insert_buffer.get(
            self.__metrics_table, []) + querys
예제 #3
0
    def exit(self, error_code: int = SUCCESS_CODE) -> NoReturn:
        """Executes finishing tasks and exits sppmon. To be called every time.

        Executes finishing tasks and displays error messages.
        Specify only error message if something did went wrong.
        Use Error codes specified at top of module.
        Does NOT return.

        Keyword Arguments:
            error {int} -- Errorcode if a error occured. (default: {0})
        """

        # error with the command line arguments
        # dont store runtime here
        if (error_code == ERROR_CODE_CMD_ARGS):
            parser.print_help()
            sys.exit(ERROR_CODE_CMD_ARGS)  # unreachable?
        if (error_code == ERROR_CODE_START_ERROR):
            ExceptionUtils.error_message(
                "Error when starting SPPMon. Please review the errors above")
            sys.exit(ERROR_CODE_START_ERROR)

        script_end_time = SppUtils.get_actual_time_sec()
        LOGGER.debug("Script end time: %d", script_end_time)

        try:
            if (not self.ignore_setup):
                self.store_script_metrics()

                if (self.influx_client):
                    self.influx_client.disconnect()
                if (self.rest_client):
                    self.rest_client.logout()

        except ValueError as error:
            ExceptionUtils.exception_info(
                error=error,
                extra_message="Error occured while exiting sppmon")
            error_code = ERROR_CODE

        self.remove_pid_file()

        # Both error-clauses are actually the same, but for possiblility of an split between error cases
        # always last due beeing true for any number != 0
        if (error_code == ERROR_CODE or error_code):
            ExceptionUtils.error_message(
                "Error occured while executing sppmon")
        elif (not self.ignore_setup):
            LOGGER.info("\n\n!!! script completed !!!\n")

        print(
            f"check log for details: grep \"PID {os.getpid()}\" {self.log_path} > sppmon.log.{os.getpid()}"
        )
        sys.exit(error_code)
    def exit(self, error_code: int = False) -> NoReturn:
        """Executes finishing tasks and exits sppmon. To be called every time.

        Executes finishing tasks and displays error messages.
        Specify only error message if something did went wrong.
        Use Error codes specified at top of module.
        Does NOT return.

        Keyword Arguments:
            error {int} -- Errorcode if a error occured. (default: {False})
        """

        # error with the command line arguments
        # dont store runtime here
        if (error_code == ERROR_CODE_CMD_LINE):
            prog_args = []
            prog_args.append(sys.argv[0])
            prog_args.append("--help")
            os.execv(sys.executable, ['python'] + prog_args)
            sys.exit(ERROR_CODE_CMD_LINE)  # unreachable?

        script_end_time = SppUtils.get_actual_time_sec()
        LOGGER.debug("Script end time: %d", script_end_time)

        try:
            if (not self.ignore_setup):
                self.store_script_metrics()

                if (self.influx_client):
                    self.influx_client.disconnect()
                if (self.rest_client):
                    self.rest_client.logout()

        except ValueError as error:
            ExceptionUtils.exception_info(
                error=error,
                extra_message="Error occured while exiting sppmon")
            error_code = ERROR_CODE

        if (not error_code):
            LOGGER.info("\n\n!!! script completed !!!\n")

        self.remove_pid_file()

        # Both clauses are actually the same, but for clarification, always last due always beeing true for any number
        if (error_code == ERROR_CODE or error_code):
            ExceptionUtils.error_message(
                "Error occured while executing sppmon")

        print(
            f"check log for details: grep \"PID {os.getpid()}\" {self.log_path} > sppmon.log.{os.getpid()}"
        )
        sys.exit(error_code)
예제 #5
0
    def _parse_free_cmd(
            ssh_command: SshCommand,
            ssh_type: SshTypes) -> Tuple[str, List[Dict[str, Any]]]:
        """Parses the result of the `free` command, splitting it into its parts.

        Arguments:
            ssh_command {SshCommand} -- command with saved result
            ssh_type {SshTypes} -- type of the client

        Raises:
            ValueError: no command given or no result saved
            ValueError: no ssh type given

        Returns:
            Tuple[str, List[Dict[str, Any]]] -- Tuple of the tablename and a insert list
        """
        if (not ssh_command or not ssh_command.result):
            raise ValueError("no command given or empty result")
        if (not ssh_type):
            raise ValueError("no sshtype given")
        if (not ssh_command.table_name):
            raise ValueError("need table name to insert parsed value")

        result_lines = ssh_command.result.splitlines()
        header = result_lines[0].split()
        header.insert(0, 'name')
        values: List[Dict[str, Any]] = list(
            map(lambda row: dict(zip(header, row.split())),
                result_lines[1:]))  # type: ignore

        (time_key, _) = SppUtils.get_capture_timestamp_sec()
        for row in values:
            # remove ':' from name
            row['name'] = row['name'][:-1]

            # set default needed fields
            row['hostName'] = ssh_command.host_name
            row['ssh_type'] = ssh_type.name
            row[time_key] = SppUtils.get_actual_time_sec()

            # recalculate values to be more usefull
            if ('available' in row):
                row['free'] = int(row.pop('available'))
                row['used'] = int(row['total']) - int(row['free'])

        return (ssh_command.table_name, values)
예제 #6
0
    def __insert_metrics_to_buffer(self,
                                   keyword: Keyword,
                                   table: Table,
                                   duration_s: float,
                                   item_count: int,
                                   error: Optional[str] = None) -> None:
        """Generates statistics of influx-requests and append them to be sent

        Arguments:
            keyword {Keyword} -- Kind of query.
            tables_count {dict} -- Tables send in this batch, key is table, value is count of items.
            duration_s {Optional[float]} -- Time needed to send the batch in seconds. None if a error occured
            item_count {int} -- Ammount of queries sent to the server

        Keyword Arguments:
            error {Optional[str]} -- Error message if an error occured.

        Raises:
            ValueError: Any arg does not match the defined parameters or value is unsupported
        """
        # Arg checks
        if (list(
                filter(lambda arg: arg is None,
                       [keyword, table, duration_s, item_count]))):
            raise ValueError(
                "One of the insert metrics to influx args is None. This is not supported"
            )
        query = InsertQuery(
            table=self.__metrics_table,
            fields={
                'error': error,
                # Calculating relative duration for this part of whole query
                'duration_ms': duration_s * 1000,
                'item_count': item_count,
            },
            tags={
                'keyword': keyword,
                'tableName': table.name,
            },
            time_stamp=SppUtils.get_actual_time_sec())
        old_queries = self.__insert_buffer.get(self.__metrics_table, [])
        old_queries.append(query)
        self.__insert_buffer[self.__metrics_table] = old_queries
예제 #7
0
    def default_split(
        cls, mydict: Dict[str, Any]
    ) -> Tuple[Dict[str, str], Dict[str, Union[float, int, bool, str]], Union[
            str, int, None]]:
        """Do not use this method on purpose! Pre-Defining a table is higly recommended.
        Splits the dict into tags/fields/timestamp according to the format of value.

        Strings without spaces are tags
        Strings with spaces or double-quotations are fields
        any number or boolean is a field
        if no field is found a dummy field is inserted with a warning and a debug message.

        Arguments:
            mydict {dict} -- dict with colums as keys. None values are ignored

        Raises:
            ValueError: Dictonary is not provided or empty

        Returns:
            (dict, dict, int) -- Tuple of tags, fields and timestamp
        """
        if (not mydict):
            raise ValueError("at least one entry is required to split")

        ExceptionUtils.error_message(
            "WARNING: Using default split method, one table is set up only temporary"
        )
        LOGGER.debug(f"default split args: {mydict}")

        # In case only fields are recognized
        fields: Dict[str, Union[float, int, bool, str]] = {}
        tags: Dict[str, str] = {}
        time_stamp: Any = None

        for (key, value) in mydict.items():
            if (value is None):
                # skip if no value
                continue

                # Check timestamp value if it matches any of predefined time names
            if (key in cls.time_key_names):

                # self defined logtime has higher priority, only it allows a rewrite
                if (not time_stamp or key == 'logTime'):
                    time_stamp = value

                # continue in any case to avoid double insert
                continue

            if (isinstance(value, (float, int, bool))):
                fields[key] = value
            # Make a string out of Lists/Dics
            if (not isinstance(value, str)):
                value = '\"{}\"'.format(value)

            if (re.search(r"[\s\[\]\{\}\"]", value)):
                fields[key] = value
            else:
                tags[key] = value

        # at least one field is required to insert.
        if (not fields):
            ExceptionUtils.error_message(f"missing field in {mydict}")
            fields["MISSING_FIELD"] = 42

        if (time_stamp is None):
            ExceptionUtils.error_message(
                f"No timestamp value gathered when using default split, using current time: {mydict}"
            )
            time_stamp = SppUtils.get_actual_time_sec()

        return (tags, fields, time_stamp)
예제 #8
0
    def split_by_table_def(
        self, mydict: Dict[str, Any]
    ) -> Tuple[Dict[str, Any], Dict[str, Any], Union[str, int]]:
        """Split the given dict into a pre-defined set of tags, fields and a timestamp.

        None-Values and empty strings are ignored.
        If there are no fields declared, it will split by a default pattern.
        Undeclared collums will produce a warning.
        This function uses the tag/field and timestamp definiton declared within this table.

        Arguments:
            self {Table} -- Table with predefined set of tags and fields
            mydict {Dict[str, Any]} -- dict with colums as keys. None-Values are ignored

        Raises:
            ValueError: If no dict is given or not of type dict.

        Returns:
            (Dict[str, Any], Dict[str, Any], int) -- Tuple of: tags, fields, timestamp
        """

        if (not mydict):
            raise ValueError("need at least one value in dict to split")

        # if table is not defined use default split
        if (not self.fields):
            return InfluxUtils.default_split(mydict=mydict)

        # fill dicts
        # table.fields is a dict, we only need the keys
        fields: Dict[str, Any] = dict.fromkeys(self.fields.keys(), None)
        tags: Dict[str, Any] = dict.fromkeys(self.tags, None)

        # what field should be recorded as time
        time_stamp_field = self.time_key
        # helper variable to only overwrite if it is not the time_stamp_field
        time_overwrite_allowed = True
        # actualy timestamp saved
        time_stamp: Union[str, int, None] = None

        for (key, value) in mydict.items():

            # Ignore empty entrys
            if (value is None or (isinstance(value, str) and not value)):
                continue

            # Check timestamp value if it matches any of predefined time names
            if (key in time_stamp_field or key in InfluxUtils.time_key_names):

                # sppmonCTS has lowest priority, only set if otherwise None
                if (time_stamp is None and key == SppUtils.capture_time_key):
                    time_stamp = value

                # time_stamp_field is highest priority. Do not overwrite it.
                elif (key is time_stamp_field):
                    time_overwrite_allowed: bool = False
                    time_stamp = value

                # if time_stamp_field is not used yet, overwrite sppmonCaptureTime or others
                elif (time_overwrite_allowed):
                    time_stamp = value

                # if no overwrite allowed, continue and drop field
                else:
                    continue

            # Otherwise check for Keys or Fields
            if (key in fields):
                fields[key] = value
            elif (key in tags):
                tags[key] = value
            elif (key in InfluxUtils.time_key_names
                  or key in time_stamp_field):
                continue
            else:
                ExceptionUtils.error_message(
                    f"Not all columns for table {self.name} are declared: {key}"
                )
                # before key+"MISSING" : Removed to avoid death-circle on repeated queries.
                fields[key] = value
        if (time_stamp is None):
            ExceptionUtils.error_message(
                f"No timestamp value gathered for table {self.name}, using current time: {mydict}"
            )
            time_stamp = SppUtils.get_actual_time_sec()

        return (tags, fields, time_stamp)