Страница 1 из 2

Нагрузочное тестирование

Добавлено: 07 май 2015, 09:47
DmitryRu
Мы проводим нагрузочное тестирование генерации отчетов Вашей библиотекой - Stimulsoft Reports.Web, версия 2015.1.0.0
Цель тестирования - реально ли заменить используемый в настоящее время генератор отчетов на SSRS сервере на движок от Стимулсофт.

Методика - с помощью visual studio load test мы эмулируем посещение нашего сайта 250 пользователями одновременно. Не все из них строят отчеты одновременно, нагрузка эмулирует поведение реального пользователя.
Тем не менее, за 4 минуты строятся 750 отчетов (пользователь строит 3 отчета в своем сеансе работы).

Пока получается, что для одного пользователя время ожидания сервера достигает 11 секунд на 3 отчета, а среднее время ожидания - 4.5 сек на 3 отчета.
Аналогичные отчеты строятся на SSRS сервере быстрее - среднее время ожидания 2 сек на 3 отчета, максимальное - 3.3 секунды.
4.5 сек на 3 отчета для нас приемлемо, однако не нравится экстремум в 11 секунд - проявляется, только когда есть приличная нагрузка.
ОС: Windows Server 2008 R2, со штатным IIS, 128 Гб ОЗУ, 4 16-ядерных процессора.
Может быть, я неправильно понимаю назначение Ваших продуктов, и для корректного сравнения с SSRS нам надо развертывать Stimulsoft Report Server?

Кроме того, при кол-ве одновременно работающих пользователей более 100 логах появляется такая ошибка:

Код: Выделить всё

An exception of type 'System.ArgumentException' occurred and was caught.
------------------------------------------------------------------------
Type : System.ArgumentException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : Value does not fall within the expected range.
Source : mscorlib
Help link : 
ParamName : 
Data : System.Collections.ListDictionaryInternal
TargetSite : Void ThrowExceptionForHRInternal(Int32, IntPtr)
Stack Trace :    at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
   at System.Web.Hosting.IIS7WorkerRequest.GetServerVariableInternal(String name)
   at System.Web.Hosting.IIS7WorkerRequest.GetServerVariable(String name)
   at System.Web.Hosting.IIS7WorkerRequest.IsSecure()
   at System.Web.HttpRequestWrapper.get_IsSecureConnection()
   at Stimulsoft.Report.Mvc.StiResourcesHelper.GetRequestUrl(HtmlHelper htmlHelper, Boolean relativeUrls, String controller)
   at Stimulsoft.Report.Mvc.StiMvcViewer.CreateChildControls()
   at Stimulsoft.Report.Mvc.StiMvcViewer.Render(HtmlTextWriter writer)
   at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter)
   at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter)
   at System.Web.UI.Control.RenderControl(HtmlTextWriter writer)
   at Stimulsoft.Report.Mvc.StiMvcHelper.StiMvcViewer(String ID, StiMvcViewerOptions options)
   at ASP._Page_Views_Shared_Reporting_StiMvcViewer_cshtml.Execute() in c:\XXXXX\Views\Shared\Reporting\StiMvcViewer.cshtml:line 13
   at System.Web.WebPages.WebPageBase.ExecutePageHierarchy()
   at System.Web.Mvc.WebViewPage.ExecutePageHierarchy()
   at System.Web.WebPages.StartPage.RunPage()
   at System.Web.WebPages.StartPage.ExecutePageHierarchy()
   at System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage)
   at System.Web.Mvc.RazorView.RenderView(ViewContext viewContext, TextWriter writer, Object instance)
   at System.Web.Mvc.BuildManagerCompiledView.Render(ViewContext viewContext, TextWriter writer)
   at System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass1c.<InvokeActionResultWithFilters>b__19()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass1c.<>c__DisplayClass1e.<InvokeActionResultWithFilters>b__1b()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass1c.<>c__DisplayClass1e.<InvokeActionResultWithFilters>b__1b()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)
   at System.Web.Mvc.Controller.ExecuteCore()
   at System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext)
   at System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext)
   at System.Web.Mvc.MvcHandler.<>c__DisplayClass6.<>c__DisplayClassb.<BeginProcessRequest>b__5()
   at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass1.<MakeVoidDelegate>b__0()
   at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _)
   at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
   at System.Web.Mvc.MvcHandler.<>c__DisplayClasse.<EndProcessRequest>b__d()
   at System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f)
   at System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action)
   at System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult)
   at System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Доп. информация:
User-Agent:Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1);
RequestType:GET
Url:/App/StiView/ShowReport/Report?Param1=qqq&Param2=bbb
CSHTML страничка выглядит так:

Код: Выделить всё

@using Stimulsoft.Report.Mvc;
@{
    Layout = null;
}
<!doctype html>
<html>
<head>
    <title>Отчет</title>
</head>

<body>
    @Html.Stimulsoft().StiMvcViewer(
    "MvcViewer",
        new StiMvcViewerOptions
    {
         Actions =
        {
             GetReportSnapshot = "GetReportSnapshot",
             ViewerEvent = "ViewerEvent",
        }
        , Localization = "~/Views/Shared/Reporting/Localization/ru.xml"
        , Toolbar =
         {
             ShowDesignButton = false,
             ShowParametersButton = false
         }
         , Server =
         {
             RequestTimeout = 30
         }
    })
</body>
</html>
Таких ошибок произошло 8 на 750 отчетов. После того, как включил опцию UseRelativeUrls = true, ошибка пропала

Re: Нагрузочное тестирование

Добавлено: 07 май 2015, 11:37
HighAley
Здравствуйте.

В MVC отчёт грузится в методе GetReportSnapshot.
Нам необходимо видеть код этого действия.
Часть времени занимает компиляция. Поэтому, если вы будете использовать скомпилированные отчёты, то на это будет тратиться меньше времени и памяти.
Возможно вам поможет статья Особенности работы со скомпилированным отчетом в нашем блоге. Также на эту тему есть статьи в базе знаний.

По поводу ошибкок. Вот код, который выполняется при выключенном UseRelativeUrls

Код: Выделить всё

            string result = string.Empty;
            if (!relativeUrls)
            {
                result = htmlHelper.ViewContext.HttpContext.Request.Url.GetLeftPart(UriPartial.Authority);
                if (htmlHelper.ViewContext.HttpContext.Request.IsSecureConnection) result = result.Replace("http:", "https:");
            }

            result += htmlHelper.ViewContext.HttpContext.Request.ApplicationPath;
Ошибка появляется при попытке получить значение свойства IsSecureConnection.

Спасибо.

Re: Нагрузочное тестирование

Добавлено: 07 май 2015, 11:58
DmitryRu
По поводу IsSecureConnection - это я понял :-)
Судя по гуглению проблемы, закэшированный Вами htmlHelper.ViewContext.HttpContext. прокисает к моменту вызова. Почему, сказать не берусь.
Видимо, как-то некорректно кэшируется.

Отчеты безусловно компилируются в .dll при первом обращении, код причешу и выложу чуть позже.

Re: Нагрузочное тестирование

Добавлено: 07 май 2015, 12:18
DmitryRu
Код довольно многословный, если надо что-то сократить для понимаемости, могу сократить,
Читать лучше с конца.

Код: Выделить всё

      
        ///Загрузка определения отчета - файл .mrt из ресурсов приложения
       ///Осуществляется 1 раз, для компиляции в .dll
        private void LoadByName(string name, StiReport report)
        {
            string fullName = string.Format("{0}.{1}.mrt", _source.GetName().Name, name);
            using (var stream = _source.GetManifestResourceStream(fullName))
            {
                report.Load(stream);
                report.Dictionary.Databases.Clear();
                report.Dictionary.Databases.Add(new StiSqlDatabase("myConnection", _connectionStringExtractor.ConnectionString));
                report.Dictionary.Synchronize();
            }
        }

        ///генерирует имя .dll файла, используется в тесте
        internal string CreateDllFileName(string reportName)
        {
            return Path.Combine(RootDirectory.FullName, string.Format("{0}.dll", reportName));
        }

        /// <summary>
        ///Компилирует отчет из формата .mrt в .dll файл
        /// </summary>
        /// <param name="reportName">Имя ресурса с определением отчета</param>
        /// <param name="lowerReportName">имя ключа в словаре имен скомпилированных файлов</param>
        /// <param name="fileName">имя скомпилированного .dll</param>
        internal void CreateCompiledReport(string reportName, string lowerReportName, string fileName)
        {
            string tempFileName;
            if (ReportFileNames.TryGetValue(lowerReportName, out tempFileName))
            {
                _log.Debug(LogCategories.Reporting, "Locked double check. File exists now: {0}", tempFileName);
                return;
            }

            if (File.Exists(fileName))
            {
                _log.Debug(LogCategories.Reporting, "Deleting existing dll Report: {0}", fileName);
                File.Delete(fileName);
            }
            using (var report = new StiReport())
            {
                LoadByName(reportName, report);
                _log.Debug(LogCategories.Reporting, "Compiling report to {0}", fileName);
                var s = new Stopwatch();
                s.Start();
                report.Compile(fileName, StiOutputType.ClassLibrary);
                s.Stop();
                _log.Debug(LogCategories.Reporting, "Report {0} compiled in {1} ms", reportName, s.ElapsedMilliseconds);
            }
            if (!ReportFileNames.TryAdd(lowerReportName, fileName))
            {
                throw new InternalApplicationException(string.Format(
                    "Could not add a compiled report to ReportFileNames. lowerReportName = {0}, fileName = {1}", 
                    lowerReportName, fileName));
            }
        }

        ///Словарь  вида UserFriedlyИмя отчета ==> имя скомпилированного .dll файла
        internal readonly ConcurrentDictionary<string, string> ReportFileNames = new ConcurrentDictionary<string, string>();

        private readonly object _lock = new object();

        /// <summary>
        /// Возвращает скомпилированный отчет, переменные отчета пока не инициализированы 
        /// </summary>
        /// <param name="reportName">user-friendly имя отчета</param>
        /// <returns>скомпилированный отчет</returns>
        internal StiReport LoadCompiledReport(string reportName)
        {
            ContractValidator.StringNotNullOrWhiteSpace(reportName, "reportName");
            var lowerName = reportName.ToLowerInvariant().Trim();
            string fileName;
            if (!ReportFileNames.TryGetValue(lowerName, out fileName))
            {
                fileName = CreateDllFileName(reportName);
                lock (_lock)
                {
                    CreateCompiledReport(reportName, lowerName, fileName);
                }
            }
            return StiReport.GetReportFromAssembly(fileName, true);
        }

        /// <summary>
        /// Присваивание отчету параметров, которые пользователь передал через URL отчета
        /// </summary>
        /// <param name="membershipId">кто запросил отчет</param>
        /// <param name="parameterValues">что введено в строке URL</param>
        /// <param name="mrt">скомплированный отчет</param>
        /// <returns>Отчет с присвоенными значениями параметров (переменных)</returns>
        internal StiReport AssingReportVariables(long membershipId, NameValueCollection parameterValues, StiReport mrt)
        {
            var parameters = ParseParametersFromUrl(membershipId, parameterValues);
            foreach (StiVariable variable in mrt.Dictionary.Variables)
            {
                if (!variable.ReadOnly) //readonly - пользователь не задает
                {
                    var parameter = parameters.GetReportParameterValue(variable.Name);
                    if (parameter != null)
                    {
                        try
                        {
                            mrt[variable.Name] = parameter.AsStiVariable;
                        }
                        catch (ArgumentException e)
                        {
                            e.Data.Add("variable.Name", variable.Name);
                            throw;
                        }
                    }
                }
            }
            return mrt;
        }
    
        /// <summary>
        /// Создает отчет в формате Стимул софт, с заполненным значениями параметров (из QueryString)
        /// </summary>
        /// <param name="reportName">Строковое имя отчета, например, MyReport</param>
        /// <param name="membershipId">учетка юзера</param>
        /// <param name="parameterValues">Параметры отчета, заданные в URL HTTP запроса</param>
        /// <returns>отчет в формате Стимул софт</returns>
        public StiReport InitializeReportBy(string reportName, long membershipId, NameValueCollection parameterValues)
        {
            var mrt = LoadCompiledReport(reportName);
            return AssingReportVariables(membershipId, parameterValues, mrt);
        }

        /// <summary>
        /// Вызывается из компонента StiMvcViewer
        /// </summary>
        /// <param name="id">Строковое имя отчета</param>
        /// <returns>данные для построения отчета</returns>
        public ActionResult GetReportSnapshot(string id)
        {
            if (string.IsNullOrWhiteSpace(id))
            {
                return HttpNotFound("No Report Name");
            }
            var user = LookupUserFromRequest();
            var mrt = InitializeReportBy(id, user.MembershipId, Request.QueryString);
            return StiMvcViewer.GetReportSnapshotResult(HttpContext, mrt);
        }



Re: Нагрузочное тестирование

Добавлено: 07 май 2015, 16:45
HighAley
Здравствуйте.

Если у вас нету никаких скриптов в отчётах, попробуйте установить Calculation Mode в режим Interpretation. Будет ли изменение в скорости построения отчётов из mrt файлов.

Можете ли вы прислать нам проект с вашими отчётами, Мы попробуем проанализировать его работу и возможно получится что-то оптимизировать.

Спасибо.

Re: Нагрузочное тестирование

Добавлено: 07 май 2015, 17:07
DmitryRu
Скрипты я думаю, есть, например, для выделения максимального значения на диаграмме, как Вы посоветовали здесь
http://forumru.stimulsoft.com/viewtopic.php?f=8&t=4176
Это Вы называете скрипты?

Прислать шаблоны могу, на support@ ? Сначала еще раз попробую убедиться, что заминка именно в библиотеках Стимулсофт, и если не удастся уменьшить пиковое время в 11 секунд, пришлю.

А по ошибке Value does not fall within the expected range ничего делать не будете?

Спасибо

Re: Нагрузочное тестирование

Добавлено: 08 май 2015, 14:15
DmitryRu
Сегодня выполнили нагрузочное тестирование на другом, менее мощном сервере.
Результаты более стабильны, среднее время ожидания отчета отличается от максимального на 40%
Потому, придется провести еще эксперимент, чтобы понять чем было вызвано трехкратное увеличение длительности формирования отчета в предыдущем случае.

Тем не менее, сервер SSRS стабильно строит подобные по сложности отчеты на 30-40% быстрее, несмотря на то, что построение отчета на сервере SSRS требует дополнительного вызова по протоколу http (обращение к SSRS серверу по HTTP)

Кроме того, при высокой нагрузке, из недр библиотеки Stimulsoft Reports.Web, версия 2015.1.0.0 получаем такое исключение:

Код: Выделить всё

Type : System.NullReferenceException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : Object reference not set to an instance of an object.
Source : Stimulsoft.Base
Help link : 
Data : System.Collections.ListDictionaryInternal
TargetSite : Void Add(System.String, System.String, System.String)
HResult : -2147467261
Stack Trace :    at Stimulsoft.Base.Localization.StiLocalization.Add(String category, String key, String value)
   at Stimulsoft.Base.Localization.StiLocalization.LoadInternal(Stream stream)
   at Stimulsoft.Base.Localization.StiLocalization.Load(String file)
   at Stimulsoft.Report.Mvc.StiMvcHelper.StiMvcViewer(String ID, StiMvcViewerOptions options)
   at ASP._Page_Views_Shared_Reporting_StiMvcViewer_cshtml.Execute() in c:\Projects\prosoft\ES\main\code\EnergoSphere.WebClient\Views\Shared\Reporting\StiMvcViewer.cshtml:line 13
   at System.Web.WebPages.WebPageBase.ExecutePageHierarchy()
   at System.Web.Mvc.WebViewPage.ExecutePageHierarchy()
   at System.Web.WebPages.StartPage.ExecutePageHierarchy()
   at System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage)
   at System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass1c.<InvokeActionResultWithFilters>b__19()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)
   at System.Web.Mvc.Controller.ExecuteCore()
   at System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext)
   at System.Web.Mvc.MvcHandler.<>c__DisplayClass6.<>c__DisplayClassb.<BeginProcessRequest>b__5()
   at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass1.<MakeVoidDelegate>b__0()
   at System.Web.Mvc.MvcHandler.<>c__DisplayClasse.<EndProcessRequest>b__d()
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
Исключение возникло 7 раз при построении 750 отчетов. Текст cshtml странички я уже приводил

Re: Нагрузочное тестирование

Добавлено: 08 май 2015, 15:17
HighAley
Здравствуйте.

По поводу скорости работы в целом: SSRS - это нэйтивное С++ приложение, а у нас C#, что немного медленнее.

Всё, что мы можем для вас сделать -- это попросить простой пример проекта с отчетами, тогда мы проанализируем и возможно что-то сможем сделать для ускорения.

По поводу исключения вы же писали, что оно пропало после включения опцию UseRelativeUrls = true.

Спасибо.

Re: Нагрузочное тестирование

Добавлено: 08 май 2015, 15:36
DmitryRu
Нет, это другое исключение, и оно проявляется при включенной опции UseRelativeUrls = true

Сейчас зашел в каталог
C:\...ram Files\Microsoft SQL Server\MSRS10_50.MSSQLSERVER\Reporting Services\ReportServer\bin
что-то не похож SSRS сервер на C++ приложение, куча сборок .NET
Ну в целом я понимаю, что Вы мне ничем особым помочь не можете по поводу "SSRS работает быстрее", я если сам не разберусь, что замедляет мои .mrt файлы, попробую прислать их Вам, там проблема в создании наборов данных - у нас они вычисляются хранимой процедурой.

Спасибо

Re: Нагрузочное тестирование

Добавлено: 08 май 2015, 16:15
HighAley
Здравствуйте.
DmitryRu писал(а):Нет, это другое исключение, и оно проявляется при включенной опции UseRelativeUrls = true
Мы изучили данную проблему. Она возникает при загрузке файла локализации.
Постараемся исправить на следующей неделе.

Спасибо.