Измерение времени выполнения функции внутри ядра linux
Я использую крючки модуля безопасности Linux, чтобы добавить некоторые пользовательские функции для системного вызова recv(). Я хочу измерить накладные расходы этой функции по сравнению с нетронутой recv(). Я написал простой сервер tcp, который я запускаю с моим модулем и без него. Этот tcp-сервер вызывает функцию recv() "N" количество раз. Он измеряет время, затраченное на каждый recv, с чем-то вроде:
clock_gettime(before);
recv()
clock_gettime(after);
global_time += after - before.
В конце я печатаю среднее время для одного recv() с "global_time/N". Позволяет называть это время как "user_space_avg_recv".
Внутри моего модуля я хочу поместить функции измерения времени, чтобы вычислить точное время выполнения моего крючка. Я попробовал 3 метода.
-
Я использовал jiffies следующим образом:
sj = jiffies;
my_hook();
ej = jiffies;
current->total_oh = ej - sj;
Но я вижу, что нет никакой разницы между значениями sj и ej. Следовательно, total_oh не изменяется.
-
Я использовал current_kernel_time(), так как я думал, что он возвращает время в наносекундах. Однако, опять же, не было никакой разницы в до и после времени.
-
Я использовал get_cycles. Я печатаю полные циклы, когда процесс завершается. Однако, когда я конвертирую эти общие значения циклов в миллисекунды, он выходит намного больше, чем
"user_space_avg_recv". Это не имеет смысла, поскольку измеренное значение внутри ядра всегда будет меньше значения времени, измеренного от пользовательского пространства. Это может означать, что я либо не измеряю, используя правильный API, либо делаю ошибку при преобразовании значения из циклов в миллисекунды.
Я использую следующую формулу для преобразования циклов в миллисекунды:
avg overhead of my hook in milliseconds =
(((cycles / 2.99) / 10^6) / N)
2,99, потому что моя тактовая частота 2,99 ГГц
Некоторые моменты:
-
Моя программа пространства пользователя привязана к одному ядру с использованием сродства с привязкой.
-
Я использую ядро 2.6.22.14
-
Чтобы отключить ядро от переключения контекстов во внутреннем ящике, я использую preempt_disable() и preempt_enable(). Таким образом, он не будет считать время выполнения других потоков ядра. Даже тогда, так как мой крючок использует некоторые операции ввода-вывода, мой поток может освободить элемент управления, или может произойти прерывание, которое может увеличить общее количество циклов.
Вопрос:
Как я могу точно измерить время выполнения функции внутри ядра?
Ответы
Ответ 1
Вы можете использовать API-интерфейс функции, чтобы получить трассировку всех вызовов и возвратов функций с использованием высокоточных временных меток. Сюда входят события прерывания и контекстные переключатели. Затем вы можете проанализировать полученную трассировку в пользовательском пространстве, чтобы получить точное представление о том, как долго ваша функция будет работать.
Если вы не можете использовать API-интерфейс функции tracer, вы можете вызвать вызов do_gettimeofday()
, чтобы получить метку времени с микросекундным разрешением или getnstimeofday()
для разрешения наносекунд. Это те же функции, что и пользовательское пространство gettimeofday()
. Конечно, для очень быстрых функций это может оказаться недостаточной точностью; любой более высокой точности, чем это, и вам, вероятно, придется копаться в таймерном коде, чтобы увидеть, как он реализует циклические преобразования. Отметим также, что только потому, что они имеют высокое разрешение, это не значит, что они имеют такую высокую точность, но они должны быть полезны для целей бенчмаркинга.
Обратите внимание, что любая форма трассировки приведет к дополнительной задержке - do_gettimeofday()
требует нескольких операций слияния и замены атома, а ftrace помещает код регистрации в каждую отдельную функцию pre- и post-amble. Вы должны учитывать это при интерпретации результатов.
Ответ 2
Я не уверен, что вы получите результат, который хотите, но мы используем следующий код, чтобы иметь микросекунды.
double Microsecs()
{
static struct timeval _t;
static struct timezone tz;
gettimeofday(&_t, &tz);
return (double)_t.tv_sec + (double)_t.tv_usec/(1000*1000);
}
Чем вы называете это до и после звонка, который вы хотите, и посмотрите, сколько раз он.
Мы использовали этот метод для оценки операций чтения/записи/поиска по времени IO, чтобы оспорить производительность, и у нас хорошие результаты.
НТН.
Ответ 3
Вы пытались использовать OProfile?