# SPDX-License-Identifier: Apache-2.0 # # The OpenSearch Contributors require contributions made to # this file be licensed under the Apache-2.0 license or a # compatible open source license. # # Modifications Copyright OpenSearch Contributors. See # GitHub history for details. # # Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright # ownership. Elasticsearch B.V. licenses this file to you under # the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. module OpenSearch module Benchmarking # Class encapsulating formatting and indexing the results from a benchmarking run. # # @since 7.0.0 class Results attr_reader :raw_results # String constant for unit of time elapsed. # # @since 7.0.0 MILLISECONDS = 'milliseconds'.freeze # Create a Results object. # # @example Create a results object. # Benchmarking::Results.new(task, [...], options) # # @param [ OpenSearch::Benchmarking ] task The task that executed the benchmarking run. # @param [ Array ] results An array of the results. # @param [ Hash ] options The options. # # @since 7.0.0 def initialize(task, results, options = {}) @task = task @raw_results = results.map { |r| r * 1000 } @options = options end # Index the results document into opensearch. # # @example Index the results. # results.index!(client) # # @param [ OpenSearch::Client ] client The client to use to index the results. # # @return [ Hash ] The results document. # # @since 7.0.0 def index!(client) create_index!(client) client.index(index: index_name, body: results_doc) results_doc end # The document recording the benchmarking run results, to index into the results cluster. # # @example Get the results document. # results.results_doc # # @return [ Hash ] The results document. # # @since 7.0.0 def results_doc @results_doc ||= begin { '@timestamp' => Time.now.iso8601, event: event_doc, agent: agent_doc, server: server_doc } end end private attr_reader :options DEFAULT_INDEX_NAME = 'benchmarking_results'.freeze DEFAULT_METRICS = ['median'].freeze CLIENT_NAME = 'opensearch-ruby-client'.freeze COMPLEXITIES = { OpenSearch::Benchmarking::Simple => :simple, OpenSearch::Benchmarking::Complex => :complex }.freeze def action_iterations options[:action_iterations] end def index_name options[:index_name] || DEFAULT_INDEX_NAME end def create_index!(client) unless client.indices.exists?(index: index_name) client.indices.create(index: index_name) end end def event_doc { description: description, category: category, action: action, duration: duration, statistics: statistics_doc, repetitions: repetitions_doc }.tap do |doc| doc.merge!(dataset: dataset, dataset_details: dataset_details) if dataset end end def description @task.description end def category COMPLEXITIES[@task.class] end def action @options[:operation] end def dataset @options[:dataset] end def dataset_details { size: @options[:dataset_size], num_documents: @options[:dataset_n_documents] } end def duration @options[:duration] * 1000 end def statistics_doc { unit: MILLISECONDS, mean: mean, median: median, max: max, min: min, standard_deviation: standard_deviation } end def median raw_results.sort![raw_results.size / 2 - 1] end def mean raw_results.inject { |sum, el| sum + el }.to_f / raw_results.size end def max raw_results.max end def min raw_results.min end def standard_deviation return 0 if raw_results.size < 2 Math.sqrt(sample_variance) end def sample_variance m = mean sum = raw_results.inject(0) { |sum, i| sum +(i-m)**2 } sum/(raw_results.length - 1).to_f end def repetitions_doc { warmup: @task.warmup_repetitions, measured: @task.measured_repetitions, iterations: action_iterations } end def agent_doc { version: OpenSearch::VERSION, name: CLIENT_NAME, git: git_doc, language: language_doc, os: client_os_doc, adapter: adapter } end def adapter @task.client_adapter end def git_doc sha = `git rev-parse HEAD` branch = /\* (.+)/.match(`git branch`)[1] commit_message = `git log -1 --pretty=%B` repository = 'opensearch-ruby' { branch: branch, sha: sha.chomp, commit_message: commit_message.gsub(/\n/, ''), repository: repository.chomp } end def language_doc version = [ RUBY_VERSION, RUBY_PLATFORM, RbConfig::CONFIG['build'] ].compact.join(', ') { runtime_version: version } end def client_os_doc { platform: platform, type: type, architecture: architecture } end def type (RbConfig::CONFIG && RbConfig::CONFIG['host_os']) ? RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase : 'unknown' end def architecture RbConfig::CONFIG['target_cpu'] end def platform [ @platform, RUBY_VERSION, RUBY_PLATFORM, RbConfig::CONFIG['build'] ].compact.join(', ') end def server_doc { version: @task.server_version, nodes_info: @task.nodes_info } end end end end