xenadmin/XenModel/Utils/Util.cs
Konstantina Chremmou 8beae03d35 CP-38583: suggested modifications. (And a couple of code smells).
Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
2022-06-15 13:05:11 +01:00

586 lines
21 KiB
C#

/* Copyright (c) Citrix Systems, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms,
* with or without modification, are permitted provided
* that the following conditions are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
namespace XenAdmin
{
public enum RoundingBehaviour
{
Up, Down, Nearest, None
}
/// <summary>
/// Miscellaneous utility functions
/// </summary>
public static class Util
{
public const long BINARY_KILO = 1024;
public const long BINARY_MEGA = BINARY_KILO * BINARY_KILO;
public const long BINARY_GIGA = BINARY_KILO * BINARY_MEGA;
public const long BINARY_TERA = BINARY_KILO * BINARY_GIGA;
public const long BINARY_PETA = BINARY_KILO * BINARY_TERA;
public const long DEC_KILO = 1000;
public const long DEC_MEGA = DEC_KILO * DEC_KILO;
public const long DEC_GIGA = DEC_KILO * DEC_MEGA;
public const long DEC_TERA = DEC_KILO * DEC_GIGA;
/// <summary>
/// The default iSCSI filer port.
/// </summary>
public const UInt16 DEFAULT_ISCSI_PORT = 3260;
public static string MemorySizeStringSuitableUnits(double bytes, bool showPoint0Decimal)
{
if (bytes >= 1 * BINARY_GIGA)
{
string format = Messages.VAL_GB_ONE_DECIMAL;
int dp = 1;
double valGB = bytes / BINARY_GIGA;
if (valGB > 100)
{
dp = 0;
format = Messages.VAL_GB;
}
if(!showPoint0Decimal)
{
format = Messages.VAL_GB;
}
return string.Format(format, Math.Round(valGB, dp, MidpointRounding.AwayFromZero));
}
else if (bytes >= 1 * BINARY_MEGA)
{
return string.Format(Messages.VAL_MB, Math.Round(bytes / BINARY_MEGA));
}
else if (bytes >= 1 * BINARY_KILO)
{
return string.Format(Messages.VAL_KB, Math.Round(bytes / BINARY_KILO));
}
if(bytes == 0)
{
return bytes.ToString();
}
else
{
return string.Format(Messages.VAL_B, bytes);
}
}
/// <summary>
/// Returns the string in suitable units and when the number of bytes is 0 the units are the ones given in formatStringWhenZero.
/// E.g. : if bytes = 0, showPoint0Decimal = false, formatStringWhenZero = Messages.VAL_MB, then the function will return "0 MB".
/// </summary>
/// <param name="bytes"></param>
/// <param name="showPoint0Decimal"></param>
/// <param name="formatStringWhenZero"></param>
/// <returns></returns>
public static string MemorySizeStringSuitableUnits(double bytes, bool showPoint0Decimal, string formatStringWhenZero)
{
if (bytes == 0)
{
return string.Format(formatStringWhenZero, bytes);
}
else
{
return MemorySizeStringSuitableUnits(bytes, showPoint0Decimal);
}
}
public static string DataRateString(double bytesPerSec)
{
string unit;
string value = ByteSizeString(bytesPerSec, 1, true, out unit);
return string.Format(Messages.VAL_FORMAT, value, unit);
}
public static string DataRateValue(double bytesPerSec, out string unit)
{
return ByteSizeString(bytesPerSec, 1, true, out unit);
}
public static string DiskSizeString(ulong bytes)
{
string value = ByteSizeString(bytes, 1, false, out var unit);
return string.Format(Messages.VAL_FORMAT, value, unit);
}
public static string DiskSizeString(long bytes, string format = null)
{
return DiskSizeString(bytes, 1, format);
}
public static string DiskSizeString(long bytes, int dp, string format = null)
{
ulong abs = (ulong)Math.Abs(bytes);
string unit;
string value = ByteSizeString(abs, dp, false, out unit, format);
return string.Format(Messages.VAL_FORMAT, value, unit);
}
public static string DiskSizeStringWithoutUnits(long bytes)
{
return ByteSizeString(bytes, 1, false, out _);
}
public static string MemorySizeStringVariousUnits(double bytes)
{
string unit;
string value = ByteSizeString(bytes, 0, false, out unit);
return string.Format(Messages.VAL_FORMAT, value, unit);
}
public static string MemorySizeValueVariousUnits(double bytes, out string unit)
{
return ByteSizeString(bytes, 0, false, out unit);
}
private static string ByteSizeString(double bytes, int decPlaces, bool isRate, out string unit, string format = null)
{
if (isRate)
{
if (bytes >= DEC_TERA)
{
unit = Messages.VAL_TERRATE;
var result = Math.Round(bytes / DEC_TERA, decPlaces);
return string.IsNullOrEmpty(format) ? result.ToString() : result.ToString(format);
}
if (bytes >= DEC_GIGA)
{
unit = Messages.VAL_GIGRATE;
var result = Math.Round(bytes / DEC_GIGA, decPlaces);
return string.IsNullOrEmpty(format) ? result.ToString() : result.ToString(format);
}
if (bytes >= DEC_MEGA)
{
unit = Messages.VAL_MEGRATE;
var result = Math.Round(bytes / DEC_MEGA, decPlaces);
return string.IsNullOrEmpty(format) ? result.ToString() : result.ToString(format);
}
if (bytes >= DEC_KILO)
{
unit = Messages.VAL_KILRATE;
var result = Math.Round(bytes / DEC_KILO, decPlaces);
return string.IsNullOrEmpty(format) ? result.ToString() : result.ToString(format);
}
unit = Messages.VAL_RATE;
return bytes.ToString();
}
if (bytes >= BINARY_TERA)
{
unit = Messages.VAL_TERB;
var result = Math.Round(bytes / BINARY_TERA, decPlaces);
return string.IsNullOrEmpty(format) ? result.ToString() : result.ToString(format);
}
if (bytes >= BINARY_GIGA)
{
unit = Messages.VAL_GIGB;
var result = Math.Round(bytes / BINARY_GIGA, decPlaces);
return string.IsNullOrEmpty(format) ? result.ToString() : result.ToString(format);
}
if (bytes >= BINARY_MEGA)
{
unit = Messages.VAL_MEGB;
var result = Math.Round(bytes / BINARY_MEGA, decPlaces);
return string.IsNullOrEmpty(format) ? result.ToString() : result.ToString(format);
}
if (bytes >= BINARY_KILO)
{
unit = Messages.VAL_KILB;
var result = Math.Round(bytes / BINARY_KILO, decPlaces);
return string.IsNullOrEmpty(format) ? result.ToString() : result.ToString(format);
}
unit = Messages.VAL_BYTE;
return bytes.ToString();
}
/// <summary>
/// nothing actually is measured in nanoseconds, we actually output microseconds
/// </summary>
public static string NanoSecondsString(double t)
{
string unit;
string value = NanoSecondsValue(t, out unit);
return string.Format(Messages.VAL_FORMAT_SECONDS, value, unit);
}
public static string NanoSecondsValue(double t, out string unit)
{
if (t >= DEC_GIGA)
{
unit = Messages.VAL_SEC;
return (t / DEC_GIGA).ToString("0");
}
if (t >= DEC_MEGA)
{
unit = Messages.VAL_MILSEC;
return (t / DEC_MEGA).ToString("0");
}
if (t >= DEC_KILO)
{
unit = Messages.VAL_MICSEC;
return (t / DEC_KILO).ToString("0");
}
unit = Messages.VAL_NANOSEC;
return t.ToString("0");
}
public static string MilliWattString(double t)
{
string unit;
string value = MilliWattValue(t, out unit);
return string.Format(Messages.VAL_FORMAT, value, unit);
}
public static string MilliWattValue(double t, out string unit)
{
if (t >= DEC_GIGA)
{
unit = Messages.VAL_MWATT;
return (t / DEC_GIGA).ToString("0");
}
if (t >= DEC_MEGA)
{
unit = Messages.VAL_KILOWATT;
return (t / DEC_MEGA).ToString("0");
}
if (t >= DEC_KILO)
{
unit = Messages.VAL_WATT;
return (t / DEC_KILO).ToString("0");
}
unit = Messages.VAL_MILWATT;
return t.ToString("0");
}
private static double DecimalAdjustment(double value, RoundingBehaviour rounding, int decimalPlaces)
{
int decimalsAdjustment = (int)Math.Pow(10, decimalPlaces);
switch (rounding)
{
case RoundingBehaviour.None:
return value;
case RoundingBehaviour.Down:
return Math.Floor(value * decimalsAdjustment) / decimalsAdjustment;
case RoundingBehaviour.Up:
return Math.Ceiling(value * decimalsAdjustment) / decimalsAdjustment;
case RoundingBehaviour.Nearest:
default:
return (Math.Round(value, decimalPlaces, MidpointRounding.AwayFromZero));
}
}
public static double ToTB(double bytes, RoundingBehaviour rounding, int decimalPlaces)
{
double value = bytes / BINARY_TERA;
return DecimalAdjustment(value, rounding, decimalPlaces);
}
public static double ToGB(double bytes, RoundingBehaviour rounding, int decimalPlaces)
{
double value = bytes / BINARY_GIGA;
return DecimalAdjustment(value, rounding, decimalPlaces);
}
public static double ToMB(double bytes, RoundingBehaviour rounding, int decimalPlaces = 0)
{
double value = bytes / BINARY_MEGA;
return DecimalAdjustment(value, rounding, decimalPlaces);
}
public static double CorrectRoundingErrors(double amount)
{
// Special case to cope with choosing an amount that's a multiple of 0.1G but not 0.5G --
// sending it to the server as the nearest byte and getting it back later --
// and finding it's fractionally changed, messing up our spinner permitted ranges.
double amountRounded = ToGB(amount, RoundingBehaviour.Nearest, 1) * BINARY_GIGA;
double roundingDiff = amountRounded - amount;
if (roundingDiff > -1.0 && roundingDiff < 1.0) // within 1 byte: although I think it will always be positive in the case we want to correct
return amountRounded;
else
return amount;
}
#region DateTime Utils
public const long TicksBefore1970 = 621355968000000000;
public static readonly string[] Iso8601DateFormats = {"yyyyMMddTHH:mm:ssZ", "yyyy-MM-ddTHH:mm:ssZ"};
public static readonly string[] NonIso8601DateFormats = { "yyyy-MM-dd" };
public static DateTime GetUnixMinDateTime()
{
return new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
}
public static long TicksToSecondsSince1970(long ticks)
{
return (long)Math.Floor(new TimeSpan(ticks - TicksBefore1970).TotalSeconds);
}
public static bool TryParseIso8601DateTime(string toParse, out DateTime result)
{
return DateTime.TryParseExact(toParse, Iso8601DateFormats, CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out result);
}
public static bool TryParseNonIso8601DateTime(string toParse, out DateTime result)
{
return DateTime.TryParseExact(toParse, NonIso8601DateFormats, CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out result);
}
public static string ToISO8601DateTime(DateTime t)
{
return t.ToUniversalTime().ToString(Iso8601DateFormats[0], CultureInfo.InvariantCulture);
}
public static double ToUnixTime(DateTime time)
{
TimeSpan diff = time - GetUnixMinDateTime();
return diff.TotalSeconds;
}
public static DateTime FromUnixTime(double time)
{
DateTime bootTime = GetUnixMinDateTime();
return bootTime.AddSeconds(time);
}
public static string TimeString(long t)
{
if (t >= 120)
return string.Format(Messages.TIME_MINUTES, t / 60);
if (t > 0)
return string.Format(Messages.TIME_SECONDS, t);
return Messages.TIME_NEGLIGIBLE;
}
#endregion
internal static string GThanSize(long min)
{
return string.Format(Messages.GREATER_THAN, DiskSizeString(min));
}
internal static string LThanSize(long max)
{
return string.Format(Messages.LESS_THAN, DiskSizeString(max));
}
public static string CountsPerSecondString(double p)
{
return string.Format(Messages.VAL_FORMAT, p, Messages.COUNTS_PER_SEC_UNIT);
}
public static string SecondsPerSecondString(double p)
{
return string.Format(Messages.VAL_FORMAT, p, UnitStrings.SEC_PER_SEC_UNIT);
}
public static string MegaHertzString(double t)
{
string unit;
string value = MegaHertzValue(t, out unit);
return string.Format(Messages.VAL_FORMAT, value, unit);
}
public static string MegaHertzValue(double t, out string unit)
{
return MegaHertzValue(t, 4, out unit);
}
/// <summary>
/// Converts the input value from MHz to GHz if needed, rounding it to the specified decimal places
/// </summary>
private static string MegaHertzValue(double t, int decPlaces, out string unit)
{
if (t >= DEC_KILO)
{
unit = Messages.VAL_GIGHZ;
return Math.Round(t / DEC_KILO, decPlaces).ToString();
}
unit = Messages.VAL_MEGHZ;
return Math.Round(t, decPlaces).ToString();
}
public static string PercentageString(double fraction)
{
return string.Format("{0}%", (fraction * 100d).ToString("0.0"));
}
public static void ThrowIfParameterNull(object obj, string name)
{
if (name == null)
{
throw new ArgumentNullException("name");
}
if (name.Length == 0)
{
ThrowBecauseZeroLength("name");
}
if (obj == null)
{
throw new ArgumentNullException(name);
}
}
public static void ThrowIfStringParameterNullOrEmpty(string value, string name)
{
ThrowIfParameterNull(value, name);
if (value.Length == 0)
{
ThrowBecauseZeroLength(name);
}
}
private static void ThrowBecauseZeroLength(string name)
{
throw new ArgumentException(string.Format("{0} cannot have 0 length.", name), name);
}
/// <summary>
/// Loads the specified non-generic IEnumerable into a generic List&lt;T&gt;.
/// </summary>
/// <typeparam name="T">The type to convert each element to</typeparam>
/// <param name="input">The input non-generic IEnumerable.</param>
/// <returns>Generic List&lt;T&gt;</returns>
public static List<T> PopulateList<T>(IEnumerable input)
{
ThrowIfParameterNull(input, "input");
List<T> output = new List<T>();
foreach (T t in input)
{
output.Add(t);
}
return output;
}
/// <summary>
/// Gets a List that represents the specified IEnumerable. If the input isn't a List then one gets created by passing the input into
/// List's constructor. If the input is already a List then it is returned directly.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input">The input.</param>
/// <returns>A list for the specified enumerable</returns>
public static List<T> GetList<T>(IEnumerable<T> input)
{
if (input == null)
{
return null;
}
var list = input as List<T>;
if (list != null)
{
return list;
}
return new List<T>(input);
}
/// <summary>
/// Matches 1-65535 inclusive.
/// </summary>
/// <param name="s">The string to be parsed</param>
/// <returns>True if the specified string contains only a valid port, otherwise false.</returns>
public static bool IsValidPort(string s)
{
int port;
if (!int.TryParse(s, out port))
return false;
return 0 < port && port <= 65535;
}
public static string GetXmlNodeInnerText(XmlNode node, string xPath)
{
ThrowIfParameterNull(node, "node");
ThrowIfStringParameterNullOrEmpty(xPath, "xPath");
XmlNodeList nodes = node.SelectNodes(xPath);
if (nodes == null || nodes.Count == 0)
{
throw new InvalidOperationException("Node not found: " + xPath);
}
return nodes[0].InnerText;
}
/// <summary>
/// Get the first node with name 'value' and returns its innerText. Used for getting results of CGSL async actions.
/// </summary>
/// <param name="xml">The XML.</param>
/// <returns>The contents of the first node with name 'value'.</returns>
public static string GetContentsOfValueNode(string xml)
{
ThrowIfStringParameterNullOrEmpty(xml, "xml");
var doc = new XmlDocument();
doc.LoadXml(xml);
// If we've got this from an async task result, then it will be wrapped
// in a <value> element.
var nodes = doc.GetElementsByTagName("value");
return nodes.Count > 0 ? nodes[0].InnerText : null;
}
}
}