You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
131 lines
6.0 KiB
131 lines
6.0 KiB
# Copyright (C) 2005-2024 Splunk Inc. All Rights Reserved.
|
|
|
|
import itsi_path # noqa
|
|
from itsi_py3 import _
|
|
|
|
import sys
|
|
import uuid
|
|
import time
|
|
import json
|
|
import traceback
|
|
from splunk.clilib.bundle_paths import make_splunkhome_path
|
|
sys.path.append(make_splunkhome_path(['etc', 'apps', 'SA-ITOA', 'lib']))
|
|
from itsi.itsi_utils import ITOAInterfaceUtils
|
|
|
|
from .metrics_backfill_worker import MetricsBackfillWorker
|
|
|
|
JOB_MANAGER_PAGE = '/app/itsi/job_manager?app=&filter='
|
|
KVSTORE_COLLECTION = 'itsi_metrics_backfill_queue'
|
|
DOCS_URL = '/help?location=[itsi:4.19.1]metrics_summary_index'
|
|
|
|
|
|
class MetricsBackfillQueue(object):
|
|
def __init__(self, session_key, logger):
|
|
self.session_key = session_key
|
|
self.service = ITOAInterfaceUtils.service_connection(session_key, 'SA-ITOA')
|
|
self.logger = logger
|
|
try:
|
|
self.queue_collection = self.service.kvstore[KVSTORE_COLLECTION]
|
|
except KeyError:
|
|
self.queue_collection = None
|
|
|
|
def get_backfill_queue_info(self):
|
|
if not self.queue_collection:
|
|
return {}
|
|
return self.queue_collection.data.query()
|
|
|
|
def clear_backfill_queue(self):
|
|
self.queue_collection.data.delete()
|
|
|
|
def notify_of_start(self, batch_id):
|
|
self.logger.info('Metric index backfill process has started')
|
|
msg = {
|
|
'severity': 'info',
|
|
'value': _('Your summary index data is being migrated to the new ITSI metrics summary index. '
|
|
'[[{0}|View the import process.]] The itsi_summary_metrics index creates a more responsive UI '
|
|
'experience by increasing the performance of the searches dispatched by ITSI. '
|
|
'[[{1}|Learn more.]]').format((JOB_MANAGER_PAGE + batch_id), DOCS_URL)
|
|
}
|
|
self.service.messages.create(name='metric_backfill_started', **msg)
|
|
|
|
def notify_of_finish(self, elapsed):
|
|
self.logger.info('Metric index backfill process completed in {} minutes'.format(elapsed))
|
|
msg = {
|
|
'severity': 'info',
|
|
'value': _('Metric index backfill process completed in {} minutes').format(elapsed)
|
|
}
|
|
self.service.messages.create(name='metric_backfill_completed', **msg)
|
|
|
|
def update_queue(self, backfill_queue_info, current_hour):
|
|
backfill_queue_info['current_hour'] = current_hour
|
|
self.queue_collection.data.update(backfill_queue_info['_key'], json.dumps(backfill_queue_info))
|
|
|
|
def execute_backfill_queue(self, config_settings={}, summary_index_name='`get_itsi_summary_index`',
|
|
metrics_index_name='`get_itsi_summary_metrics_index`', sourcetype='itsi_summary:metrics'):
|
|
"""
|
|
Main function that generates the searches that will be used to backfill data from itsi_summary index into
|
|
the itsi_summary_metrics index (introduced in ITSI 4.6.0).
|
|
|
|
Uses configuration settings defined in inputs.conf (or modified through UI) for how many searches or how quickly
|
|
backfill process will run.
|
|
|
|
@type config_settings: dict
|
|
@param config_settings: the configuration settings from the modular input configuration (inputs.conf)
|
|
Configurations include:
|
|
- metrics_backfill_length: how many days back backfill should look for data
|
|
- metrics_backfill_throttle: how long the backfill process should wait in between running searches
|
|
- metrics_backfill_concurrent_searches: how many searches run at the same time
|
|
|
|
:param summary_index_name: index where the data is coming from
|
|
:param metrics_index_name: index where the data will be written to
|
|
:param sourcetype: sourcetype to be used in the backfill events/results
|
|
:return: Boolean indicating success or failure
|
|
"""
|
|
try:
|
|
backfill_queue_info = self.get_backfill_queue_info()
|
|
if not len(backfill_queue_info):
|
|
# queue is empty
|
|
return False
|
|
else:
|
|
backfill_queue_info = backfill_queue_info[0]
|
|
|
|
is_backfill_triggered = backfill_queue_info.get('is_backfill_triggered')
|
|
if not is_backfill_triggered:
|
|
return False
|
|
|
|
batch_id = str(uuid.uuid4())
|
|
|
|
self.notify_of_start(batch_id)
|
|
self.logger.info('Backfill queue info: ' + str(backfill_queue_info))
|
|
start = time.time()
|
|
|
|
# Configuration settings come from the modular input settings. Default settings:
|
|
# Lookback of 3 days, throttle 10s, 1 concurrent search
|
|
hours_lookback = int(config_settings.get('metrics_backfill_length', 3)) * 24
|
|
wait_throttle = int(config_settings.get('metrics_backfill_throttle', 10))
|
|
concurrent_searches = int(config_settings.get('metrics_backfill_concurrent_searches', 1))
|
|
current_hour = backfill_queue_info.get('current_hour', 1)
|
|
|
|
workers = [MetricsBackfillWorker(self.session_key, self.logger) for w in range(concurrent_searches)]
|
|
current_hour = 1 if current_hour < 1 else current_hour
|
|
|
|
while current_hour < hours_lookback:
|
|
for worker in workers:
|
|
if worker.is_work_done():
|
|
earliest = '-{}h@h'.format(current_hour)
|
|
latest = '-{}h@h'.format(current_hour - 1) if current_hour > 1 else 'now'
|
|
worker.migrate_summary_to_metrics(earliest, latest, batch_id, summary_index_name, metrics_index_name, sourcetype)
|
|
current_hour += 1
|
|
self.update_queue(backfill_queue_info, current_hour)
|
|
# Give the indexing tier a brief break in between each backfill search
|
|
time.sleep(wait_throttle)
|
|
|
|
elapsed = int((time.time() - start) // 60)
|
|
self.notify_of_finish(elapsed)
|
|
self.clear_backfill_queue()
|
|
return True
|
|
except Exception as e:
|
|
self.logger.error('Error running metrics_backfill: ' + str(e))
|
|
self.logger.error(str(traceback.format_exc()))
|
|
return False
|