Введение
Quantify, вставляя отладочный код в бинарный текст тестируемого модуля, замеряет временные интервалы, которые прошли между предыдущим и текущими запусками. Полученная информация отображается в нескольких видах: табличном, графическом, комбинированном.
Статистическая информация от Quantify позволит узнать какие dll библиотеки участвовали в работе приложения, узнать список всех вызванных функций с их именами, формальными параметрами вызова и с статистическим анализатором, показывающим сколько каждая функция исполнялась.
Гибкая система фильтров Quantify позволяет, не загромождая экран лишними выводами (например, системными вызовами) позволяет получать необходимую информацию либо только о внутренних, программных вызовах либо только о внешних, либо комбинируя оба подхода.
Вооружившись полученной статистикой, разработчик без труда выявит узкие места в производительности тестируемого приложения и устранит их в кратчайшие сроки.
Рисунок 1 показывает действия после выбора «File->Run», в результате которого можно выбрать имя внешнего модуля и аргументы его вызова.
В качестве параметров настройки можно выбрать метод вставки отладочного кода:
Line. Наилучший способ вставки отладочного кода. Замеряется время исполнения каждой строки тестируемого приложения.
Function. То же самое, что и для «line», но с замером для времени исполнения вызываемых функций.
Time. Осуществляет сбор временной информации и преобразует ее вы машинные циклы.
По умолчанию Quantify собирает статистическую информацию в модуле тестируемого продукта и во всех внешних библиотеках.
Начало насыщения тестируемого приложения сопровождается появлением окна инструментирования, в котором построчно отображаются все модули, вызываемые основным. Данные модули, как говорилось выше, насыщаются отладочным кодом и помещаются в специальную директорию «cache» по адресу «\rational\quantify\cache». Отметим, что первоначальный запуск инструментирвания процесс длительный, но каждый последующий вызов сокращает общее время ожидания в силу того, что вся необходимая информация уже есть в Кеше.
С точки зрения дисковой емкости, файл (кэшируемый) с отладочной информацией от Quantify вдвое длиннее своего собрата без отладочной информации.
«Run Summary»
Плавно переходим к следующей стадии тестирования, собственно, к сбору информации. По окончании процесса насыщения отладочным кодом модулей тестируемого приложения, Quantify переходит к его исполнению, производимым, обычным образом, за одним исключением: запись состояний тестируемого приложения продолжает проводиться в фоновом режиме.
Рисунок 3 демонстрирует фрагмент окна «Summary», в котором производится запись состояния тестируемого приложения. Причем, что очень примечательно, тестирование производится не только для простого приложения, но и для много поточного. В последнем случае (см. рисунок 3), тестируется каждый поток отдельно. В любом случае, даже если приложение однопоточное, то имя основного (единственного) потока именуется как «.main_0», что представляется вполне логичным.
Информационный график постепенно наполняется квадратами разного цвета, демонстрирующими текущее состояние тестируемого приложения.
Отметим некоторые из них:
Running. Начало исполнения потока;
Waiting I\O. Ожидание действий по вводу\выводу;
Blocked. Блокирование исполнения потока;
Quantify. Ожидание вызова модуля Quantify;
Exited. Окончание исполнения потока.
Важный аспект при тестировании — получение статистической информации о количестве внешних библиотек, которые вызывало основное приложение, а также элементарное описание машины, на которой проводилось тестирование. Последнее особенно важно, так как бывает, что ошибка проявляется только на процессорах строго определенной серии или производителя. Все статистические аспекты решаются внутри окна «Summary».
Следующие два примера показывают статистическую информацию:
(1) Общий отчет — «Details»:
Program Name: C:\projects\aa\Debug\aa.exe
Program Arguments:
Working Directory: C:\projects\aa\Debug
User Name: Alex
Product Version: 2002.05.00 4113
Host Name: ALEX-GOLDER
Machine Type: Intel Pentium Pro Model 8 Stepping 10
# Processors: 1
Clock Rate: 847 MHz
O/S Version: Windows NT 5.1.2600
Physical Memory: 382 MBytes
PID: 0xfbc
Start Time: 24.04.2002 14:17:38
Stop Time: 24.04.2002 14:17:52
Elapsed Time: 13330 ms
# Measured Cycles: 191748 (0 ms)
# Timed Cycles: 2489329 (2 ms)
Dataset Size (bytes): 0x4a0001.
(2) «Log»
Quantify for Windows,
Copyright (C) 1993-2001 Rational Software Corporation All rights reserved.
Version 2002.05.00; Build: 4113;
WinNT 5.1 2600 Uniprocessor Free
Instrumenting:
Far.exe 620032 bytes
ADVAPI32.DLL 549888 bytes
ADVAPI32.DLL 549888 bytes
USER32.DLL 561152 bytes
USER32.DLL 561152 bytes
SHELL32.DLL 8322560 bytes
SHELL32.DLL 8322560 bytes
WINSPOOL.DRV 131584 bytes
WINSPOOL.DRV 131584 bytes
MPR.DLL 55808 bytes
MPR.DLL 55808 bytes
RPCRT4.DLL 463872 bytes
RPCRT4.DLL 463872 bytes
GDI32.DLL 250880 bytes
GDI32.DLL 250880 bytes
MSVCRT.DLL 322560 bytes
MSVCRT.DLL 322560 bytes
SHLWAPI.DLL 397824 bytes
SHLWAPI.DLL 397824 bytes
Для разработчика или тестера информация (информационный отчет), представленная выше, способна пролить свет на те статистические данные, которые сопровождали, а точнее, формировали среду тестирования.
Следующий интереснейший способ анализа конструкции приложения — это просмотр дерева вызовов. Окно, показанное на 4 рисунке, показывает только фрагмент окна с диаграммой вызовов.
Обратите внимание на количество и последовательность вызова различных модулей потока «main». Жирная линия показывает наиболее длительные ветви (содержащие либо часто вызываемые функции, либо функции, выполнявшиеся дольше остальных). Для демонстрации возможностей Quantify было сконструировано простое приложение, состоящее из функции «main» и двух дополнительных «recursive» и «outside» (см. листинг 1).
Листинг 1. Пример тестируемого приложения, сконструированном в виде консольного приложения из Visual Studio 6.0. Язык реализации «С».
#include "stdafx.h"
//Создаем функцию-заглушку
void outside (void)
{
static int v=0;
v++;
}
//Создаем рекурсивную функцию, исполняющуюся 100 раз
int recursive(void)
{
static int i=0;
int oo;
outside();//Вызываем функцию заглушку
if(i==100){i=1;return 0;}//Обнуляем счетчик и выходим
i++;
recursive();
}
int main(int argc, char* argv[])
{
int i;
for(i=0;i<100;i++)recursive();
//Вызываем 100 раз рекурсивную функцию 100х100
return 0;
}
Приложение простое по сути, но очень содержательное, так как эффективно демонстрирует основные возможности Quantify.
В самом начале статьи мы выдвигали требование, по которому разработчикам не рекомендуется пользоваться рекурсивными функциями.
Тестеры или разработчики, увидев диаграмму вызовов, выделят функцию, находящуюся в полукруге, что является признаком рекурсивного вызова (см. рисунок).
В зависимости от того, на какой из ветвей дерева, находится курсор, выводится дополнительная статистическая информация о временном доступе к выделенной функции и к дочерним, идущим ниже.
Следующий рисунок демонстрирует статистику по функции «recursive».
Более подробно о статистике будет рассказано в следующем материале.
Список вызовов функций «Function List»
Одно из наиболее важных статистических окон. Здесь в табличном виде выводится статистическая информация по числу и времени работы каждой функции. Рисунок 6 демонстрирует окно с включенной сортировкой по числу вызовов каждой функции. В качестве дополнительной информации включен список формальных параметров вызовов функций. Подобную информацию можно получить только в том случае, когда тестируется модуль с отладочным кодом, к которому прилагается исходный текст.
Единицы измерения длительности работы функций могут быть следующими:
Микросекунды;
Миллисекунды;
Секунды;
Машинные циклы.
На рисунке приведены цифры соответствующие машинным циклам.
Полученная таблица вызовов анализируется тестером или разработчиком для выяснения узких мест в производительности приложения.
К сожалению, для принятия решения о производительности приложения, а точнее, производительности отдельных его функций можно принимать только рассматривая данный вопрос в комплексном разрезе. А именно, принимается во внимание и число вызовов каждой функции, и среднее время доступа к функции и общее время работы функции, и, наконец, то использовались ли при компиляции определенные специфические настройки компилятора.
Это комплексный подход, не предполагающий однозначного совета.
Сначала рассмотрим описание столбцов в появившейся таблице. Хотя многие из пунктов и являются интуитивно понятными, все же попробуем дать им короткое описание:
Function. Наименование функции. Можно высвечивать число и тип формальных параметров вызова данной функции.
Calls. Число вызовов. Величина абсолютная.
Function Time. Общее время исполнения всех вызовов данной функции
Max F Time. Максимальное время функции
Module. Полный путь до модуля с функцией (бинарного)
Min F Time. Минимальное время работы функции
Source File. Полный путь до исходных текстов модуля.
По любому из предложенных полей можно провести сортировку в прямом и обратном порядке, а также наложить фильтр на определенные модули, например, для проверки только внутренних модулей или только внешних.
Выделить из списка узкую функцию трудно, поскольку для правильного расчета нужно принимать во внимание и время работы функции и число вызовов. Причем, число вызовов не всегда может быть показателем медлительности функции (вызывается часто, а работает быстро).