blob: 2deee0b810ad55e790838320c2eddcaffd860430 [file] [log] [blame]
using J2N;
using J2N.Text;
using Lucene.Net.Util;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using Console = Lucene.Net.Util.SystemConsole;
namespace Lucene.Net.Benchmarks.ByTask.Utils
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.
*/
/// <summary>
/// Perf run configuration properties.
/// </summary>
/// <remarks>
/// Numeric property containing ":", e.g. "10:100:5" is interpreted
/// as array of numeric values. It is extracted once, on first use, and
/// maintain a round number to return the appropriate value.
/// <para/>
/// The config property "work.dir" tells where is the root of
/// docs data dirs and indexes dirs. It is set to either of:
/// <list type="bullet">
/// <item><description>value supplied for it in the alg file;</description></item>
/// <item><description>otherwise, value of environment variable "benchmark.work.dir";</description></item>
/// <item><description>otherwise, "work".</description></item>
/// </list>
/// </remarks>
public class Config
{
// For tests, if verbose is not turned on, don't print the props.
// LUCENENET specific - reformatted with :
private static readonly bool DEFAULT_PRINT_PROPS = SystemProperties.GetPropertyAsBoolean("tests:verbose", true);
private static readonly string NEW_LINE = Environment.NewLine;
private int roundNumber = 0;
private readonly IDictionary<string, string> props;
private readonly IDictionary<string, object> valByRound = new Dictionary<string, object>();
private readonly IDictionary<string, string> colForValByRound = new Dictionary<string, string>();
private readonly string algorithmText;
/// <summary>
/// Read both algorithm and config properties.
/// </summary>
/// <param name="algReader">From where to read algorithm and config properties.</param>
/// <exception cref="IOException">If there is a low-level I/O error.</exception>
public Config(TextReader algReader)
{
// read alg file to array of lines
IList<string> lines = new List<string>();
int lastConfigLine = 0;
string line;
while ((line = algReader.ReadLine()) != null)
{
lines.Add(line);
if (line.IndexOf('=') > 0)
{
lastConfigLine = lines.Count;
}
}
algReader.Dispose();
// copy props lines to string
MemoryStream ms = new MemoryStream();
TextWriter writer = new StreamWriter(ms);
for (int i = 0; i < lastConfigLine; i++)
{
writer.WriteLine(lines[i]);
}
// read props from string
this.props = new Dictionary<string, string>();
writer.Flush();
ms.Position = 0;
props.LoadProperties(ms);
// make sure work dir is set properly
if (!props.TryGetValue("work.dir", out string temp) || temp == null)
{
// LUCENENET specific - reformatted with :
props["work.dir"] = SystemProperties.GetProperty("benchmark:work:dir", "work");
}
if (props.TryGetValue("print.props", out temp))
{
if (temp.Equals("true", StringComparison.OrdinalIgnoreCase))
{
PrintProps();
}
}
else if (DEFAULT_PRINT_PROPS)
{
PrintProps();
}
// copy algorithm lines
var sb = new StringBuilder();
for (int i = lastConfigLine; i < lines.Count; i++)
{
sb.Append(lines[i]);
sb.Append(NEW_LINE);
}
algorithmText = sb.ToString();
}
/// <summary>
/// Create config without algorithm - useful for a programmatic perf test.
/// </summary>
/// <param name="props">Configuration properties.</param>
public Config(IDictionary<string, string> props)
{
this.props = props;
if (props.TryGetValue("print.props", out string temp))
{
if (temp.Equals("true", StringComparison.OrdinalIgnoreCase))
{
PrintProps();
}
}
else if (DEFAULT_PRINT_PROPS)
{
PrintProps();
}
}
private void PrintProps()
{
Console.WriteLine("------------> config properties:");
List<string> propKeys = new List<string>(props.Keys);
propKeys.Sort();
foreach (string propName in propKeys)
{
Console.WriteLine(propName + " = " + props[propName]);
}
Console.WriteLine("-------------------------------");
}
/// <summary>
/// Return a string property.
/// </summary>
/// <param name="name">Name of property.</param>
/// <param name="dflt">Default value.</param>
/// <returns>A string property.</returns>
public virtual string Get(string name, string dflt)
{
string[] vals;
if (valByRound.TryGetValue(name, out object temp) && temp != null)
{
vals = (string[])temp;
return vals[roundNumber % vals.Length];
}
// done if not by round
if (!props.TryGetValue(name, out string sval))
{
sval = dflt;
}
if (sval == null)
{
return null;
}
if (sval.IndexOf(':') < 0)
{
return sval;
}
else if (sval.IndexOf(":\\", StringComparison.Ordinal) >= 0 || sval.IndexOf(":/", StringComparison.Ordinal) >= 0)
{
// this previously messed up absolute path names on Windows. Assuming
// there is no real value that starts with \ or /
return sval;
}
// first time this prop is extracted by round
int k = sval.IndexOf(':');
string colName = sval.Substring(0, k - 0);
sval = sval.Substring(k + 1);
colForValByRound[name] = colName;
vals = PropToStringArray(sval);
valByRound[name] = vals;
return vals[roundNumber % vals.Length];
}
/// <summary>
/// Set a property.
/// <para/>
/// Note: once a multiple values property is set, it can no longer be modified.
/// </summary>
/// <param name="name">Name of property.</param>
/// <param name="value">Either single or multiple property value (multiple values are separated by ":")</param>
public virtual void Set(string name, string value)
{
if (valByRound.TryGetValue(name, out object temp) && temp != null)
{
throw new Exception("Cannot modify a multi value property!");
}
props[name] = value;
}
/// <summary>
/// Return an <see cref="int"/> property.
/// <para/>
/// If the property contain ":", e.g. "10:100:5", it is interpreted
/// as array of ints. It is extracted once, on first call
/// to Get() it, and a by-round-value is returned.
/// </summary>
/// <param name="name">Name of property.</param>
/// <param name="dflt">Default value.</param>
/// <returns>An <see cref="int"/> property.</returns>
public virtual int Get(string name, int dflt)
{
// use value by round if already parsed
int[] vals;
if (valByRound.TryGetValue(name, out object temp) && temp != null)
{
vals = (int[])temp;
return vals[roundNumber % vals.Length];
}
// done if not by round
if (!props.TryGetValue(name, out string sval))
{
sval = dflt.ToString(CultureInfo.InvariantCulture);
}
if (sval.IndexOf(':') < 0)
{
return int.Parse(sval, CultureInfo.InvariantCulture);
}
// first time this prop is extracted by round
int k = sval.IndexOf(':');
string colName = sval.Substring(0, k - 0);
sval = sval.Substring(k + 1);
colForValByRound[name] = colName;
vals = PropToInt32Array(sval);
valByRound[name] = vals;
return vals[roundNumber % vals.Length];
}
/// <summary>
/// Return a double property.
/// <para/>
/// If the property contain ":", e.g. "10:100:5", it is interpreted
/// as array of doubles. It is extracted once, on first call
/// to Get() it, and a by-round-value is returned.
/// </summary>
/// <param name="name">Name of property.</param>
/// <param name="dflt">Default value.</param>
/// <returns>A double property.</returns>
public virtual double Get(string name, double dflt)
{
// use value by round if already parsed
double[] vals;
if (valByRound.TryGetValue(name, out object temp) && temp != null)
{
vals = (double[])temp;
return vals[roundNumber % vals.Length];
}
// done if not by round
if (!props.TryGetValue(name, out string sval))
{
sval = dflt.ToString(CultureInfo.InvariantCulture);
}
if (sval.IndexOf(':') < 0)
{
return double.Parse(sval, CultureInfo.InvariantCulture);
}
// first time this prop is extracted by round
int k = sval.IndexOf(':');
string colName = sval.Substring(0, k - 0);
sval = sval.Substring(k + 1);
colForValByRound[name] = colName;
vals = PropToDoubleArray(sval);
valByRound[name] = vals;
return vals[roundNumber % vals.Length];
}
/// <summary>
/// Return a boolean property.
/// If the property contain ":", e.g. "true.true.false", it is interpreted
/// as array of booleans. It is extracted once, on first call
/// to Get() it, and a by-round-value is returned.
/// </summary>
/// <param name="name">Name of property.</param>
/// <param name="dflt">Default value.</param>
/// <returns>A <see cref="bool"/> property.</returns>
public virtual bool Get(string name, bool dflt)
{
// use value by round if already parsed
bool[] vals;
if (valByRound.TryGetValue(name, out object temp) && temp != null)
{
vals = (bool[])temp;
return vals[roundNumber % vals.Length];
}
// done if not by round
if (!props.TryGetValue(name, out string sval))
{
sval = dflt.ToString(); // LUCENENET NOTE: bool ignores the IFormatProvider argument, it returns the values of constants
}
if (sval.IndexOf(':') < 0)
{
return bool.Parse(sval);
}
// first time this prop is extracted by round
int k = sval.IndexOf(':');
string colName = sval.Substring(0, k - 0);
sval = sval.Substring(k + 1);
colForValByRound[name] = colName;
vals = PropToBooleanArray(sval);
valByRound[name] = vals;
return vals[roundNumber % vals.Length];
}
/// <summary>
/// Increment the round number, for config values that are extracted by round number.
/// </summary>
/// <returns>The new round number.</returns>
public virtual int NewRound()
{
roundNumber++;
StringBuilder sb = new StringBuilder("--> Round ").Append(roundNumber - 1).Append("-->").Append(roundNumber);
// log changes in values
if (valByRound.Count > 0)
{
sb.Append(": ");
foreach (string name in valByRound.Keys)
{
object a = valByRound[name];
if (a is int[] ai)
{
int n1 = (roundNumber - 1) % ai.Length;
int n2 = roundNumber % ai.Length;
sb.Append(" ").Append(name).Append(":").Append(ai[n1]).Append("-->").Append(ai[n2]);
}
else if (a is double[] ad)
{
int n1 = (roundNumber - 1) % ad.Length;
int n2 = roundNumber % ad.Length;
sb.Append(" ").Append(name).Append(":").Append(ad[n1]).Append("-->").Append(ad[n2]);
}
else if (a is string[] astr)
{
int n1 = (roundNumber - 1) % astr.Length;
int n2 = roundNumber % astr.Length;
sb.Append(" ").Append(name).Append(":").Append(astr[n1]).Append("-->").Append(astr[n2]);
}
else
{
bool[] ab = (bool[])a;
int n1 = (roundNumber - 1) % ab.Length;
int n2 = roundNumber % ab.Length;
sb.Append(" ").Append(name).Append(":").Append(ab[n1]).Append("-->").Append(ab[n2]);
}
}
}
Console.WriteLine();
Console.WriteLine(sb.ToString());
Console.WriteLine();
return roundNumber;
}
private string[] PropToStringArray(string s)
{
if (s.IndexOf(':') < 0)
{
return new string[] { s };
}
List<string> a = new List<string>();
StringTokenizer st = new StringTokenizer(s, ":");
while (st.MoveNext())
{
string t = st.Current;
a.Add(t);
}
return a.ToArray();
}
// extract properties to array, e.g. for "10:100:5" return int[]{10,100,5}.
private int[] PropToInt32Array(string s)
{
if (s.IndexOf(':') < 0)
{
return new int[] { int.Parse(s, CultureInfo.InvariantCulture) };
}
List<int> a = new List<int>();
StringTokenizer st = new StringTokenizer(s, ":");
while (st.MoveNext())
{
string t = st.Current;
a.Add(int.Parse(t, CultureInfo.InvariantCulture));
}
int[] res = new int[a.Count];
for (int i = 0; i < a.Count; i++)
{
res[i] = a[i];
}
return res;
}
// extract properties to array, e.g. for "10.7:100.4:-2.3" return int[]{10.7,100.4,-2.3}.
private double[] PropToDoubleArray(string s)
{
if (s.IndexOf(':') < 0)
{
return new double[] { double.Parse(s, CultureInfo.InvariantCulture) };
}
List<double> a = new List<double>();
StringTokenizer st = new StringTokenizer(s, ":");
while (st.MoveNext())
{
string t = st.Current;
a.Add(double.Parse(t, CultureInfo.InvariantCulture));
}
double[] res = new double[a.Count];
for (int i = 0; i < a.Count; i++)
{
res[i] = a[i];
}
return res;
}
// extract properties to array, e.g. for "true:true:false" return boolean[]{true,false,false}.
private bool[] PropToBooleanArray(string s)
{
if (s.IndexOf(':') < 0)
{
return new bool[] { bool.Parse(s) };
}
List<bool> a = new List<bool>();
StringTokenizer st = new StringTokenizer(s, ":");
while (st.MoveNext())
{
string t = st.Current;
a.Add(bool.Parse(t));
}
bool[] res = new bool[a.Count];
for (int i = 0; i < a.Count; i++)
{
res[i] = a[i];
}
return res;
}
/// <summary>
/// Gets names of params set by round, for reports title.
/// </summary>
public virtual string GetColsNamesForValsByRound()
{
if (colForValByRound.Count == 0)
{
return "";
}
StringBuilder sb = new StringBuilder();
foreach (string name in colForValByRound.Keys)
{
string colName = colForValByRound[name];
sb.Append(" ").Append(colName);
}
return sb.ToString();
}
/// <summary>
/// Gets values of params set by round, for reports lines.
/// </summary>
public virtual string GetColsValuesForValsByRound(int roundNum)
{
if (colForValByRound.Count == 0)
{
return "";
}
StringBuilder sb = new StringBuilder();
foreach (string name in colForValByRound.Keys)
{
string colName = colForValByRound[name];
string template = " " + colName;
if (roundNum < 0)
{
// just append blanks
sb.Append(Formatter.FormatPaddLeft("-", template));
}
else
{
// append actual values, for that round
valByRound.TryGetValue(name, out object a);
if (a is int[] ai)
{
int n = roundNum % ai.Length;
sb.Append(Formatter.Format(ai[n], template));
}
else if (a is double[] ad)
{
int n = roundNum % ad.Length;
sb.Append(Formatter.Format(2, ad[n], template));
}
else if (a is string[] astr)
{
int n = roundNum % astr.Length;
sb.Append(astr[n]);
}
else
{
bool[] ab = (bool[])a;
int n = roundNum % ab.Length;
sb.Append(Formatter.FormatPaddLeft("" + ab[n], template));
}
}
}
return sb.ToString();
}
/// <summary>
/// Gets the round number.
/// </summary>
public virtual int RoundNumber => roundNumber;
/// <summary>
/// Gets the algorithmText.
/// </summary>
public virtual string AlgorithmText => algorithmText;
}
}