C Linux Регулирование пропускной способности приложения

Какими способами я могу попытаться ограничить функцию send/sendto() внутри цикла. Я создаю сканер портов для своей сети, и я попробовал два метода, но они, кажется, работают только локально (они работают, когда я тестирую их на своей домашней машине, но когда я пытаюсь проверить их на другой машине, он не хочет создавать соответствующие дроссели).

метод 1

Первоначально я анализировал /proc/net/dev и читал атрибут «отправлено байтов» и основывал свое время сна на этом. Это работало локально (задержка сна настраивалась для регулировки потока полосы пропускания), но как только я попробовал это на другом сервере, также с /proc/net/dev, похоже, данные не корректировались правильно. Я запустил dstat на машине, которую сканировал локально, и она выдавала слишком много данных слишком быстро.

метод 2

Затем я попытался отследить общее количество отправляемых байтов и добавить его в переменную total_sent, которую мой поток полосы пропускания будет читать и вычислять таймер сна. Это также работало на моем локальном компьютере, но когда я попробовал это на сервере, он сказал, что он отправляет только 1-2 пакета каждый раз, когда мой поток пропускной способности проверяет total_sent, заставляя мой поток пропускной способности уменьшать сон до 0, но даже при 0 total_sent переменная не увеличилась из-за сокращения времени сна, а осталась неизменной.

В целом, мне нужен способ отслеживать пропускную способность компьютера с Linux и вычислять время сна, которое я могу передать в usleep() до или после каждого из моих вызовов сокета send/sendto(), чтобы уменьшить пропускную способность.

Редактировать: некоторые другие вещи, которые я забыл упомянуть, это то, что у меня есть функция speedtest, которая вычисляет скорость загрузки машины, и у меня есть 2 потока. Поток 1 настраивает глобальный таймер сна на основе использования полосы пропускания, а поток 2 отправляет пакеты на порты на удаленной машине, чтобы проверить, открыты ли они, и снять с них отпечаток (сейчас я просто использую пакеты udp с sendto(), чтобы проверить все это ).

Как я могу реализовать регулирование полосы пропускания для вызова send/sendto() с помощью usleep().

Редактировать. Вот код для моего потока мониторинга пропускной способности. Не беспокойтесь о структуре, это просто мой способ передачи данных в поток.

void *bandwidthmonitor_cmd(void *param)
{
  int i = 0;
  double prevbytes = 0, elapsedbytes = 0, byteusage = 0, maxthrottle = 0;

  //recreating my param struct i passed to the thread
  command_struct bandwidth = *((command_struct *)param);
  free(param);

  //set SLEEP (global variable) to a base time in case it was edited and not reset
  SLEEP = 5000;

  //find the maximum throttle speed in kb/s (takes the global var UPLOAD_SPEED
  //which is in kb/s and times it by how much bandwidth % you want to use
  //and devides by 100 to find the maximum in kb/s
  //ex: UPLOAD_SPEED = 60, throttle = 90, maxthrottle = 54
  maxthrottle = (UPLOAD_SPEED * bandwidth.throttle) / 100;
  printf("max throttle: %.1f\n", maxthrottle);

  while(1)
  {
      //find out how many bytes elapsed since last polling of the thread
      elapsedbytes = TOTAL_BYTES_SEND - prevbytes;
      printf("elapsedbytes: %.1f\n", elapsedbytes);

      //set prevbytes to our current bytes so we can have results next loop
      prevbytes = TOTAL_BYTES_SEND;

      //convert our bytes to kb/s
      byteusage = 8 * (elapsedbytes / 1024);

      //throttle control to make it adjust sleep 20 times every 30~ 
      //iterations of the loop
      if(i & 0x40)
      {
           //adjust SLEEP by 1.1 gain
           SLEEP += (maxthrottle - byteusage) * -1.1;//;


           if(SLEEP < 0){
               SLEEP = 0;
           }
           printf("sleep:%.1f\n\n", SLEEP);
      }

      //sleep the thread for a short bit then start the process over
      usleep(25000);
      //increment variable i for our iteration throttling
      i++;
  }

}

Мой поток отправки — это простая sendto() процедура в цикле while(1), отправляющая udp-пакеты для тестирования. sock — это мой sockfd, buff — это 64-байтовый массив символов, заполненный «A», а sin — это my sockaddr_in.

  while(1)
  {
    TOTAL_BYTES_SEND += 64;

    sendto(sock, buff, strlen(buff), 0, (struct sockaddr *) &sin, sizeof(sin))


    usleep(SLEEP);
  }

Я знаю, что мои функции сокета работают, потому что я вижу использование в dstat на моей локальной машине и на удаленной машине. Этот код пропускной способности работает в моей локальной системе (все переменные меняются как надо), но на сервере, который я пробовал тестировать на прошедших байтах, не меняется (всегда 64/128 на итерацию потока) и приводит к SLEEP дросселированию до 0 что теоретически должно заставить машину отправлять пакеты быстрее, но даже с SLEEP, равным 0 прошедшим байтам, остается 64/128. Я также закодировал функцию sendto() внутри оператора if, проверяющего функцию, возвращающую -1, и предупреждающую меня printf-кодом ошибки, но в тестах, которые я проводил, ее не было.


person randy newfield    schedule 26.11.2012    source источник
comment
«Затем я попытался отследить, сколько всего байтов я отправил, и добавить его в переменную total_sent, которую мой поток пропускной способности будет читать и вычислять для таймера сна». — это звучит правильно для меня, пожалуйста, опубликуйте свой код, который пытается это сделать.   -  person Alexey Feldgendler    schedule 26.11.2012
comment
@AlexeyFeldgendler: я представил часть своего кода. может ли это быть проблемой блокировки мьютекса? я очень быстро читаю и пишу в одну и ту же переменную из 2 потоков без какой-либо блокировки.   -  person randy newfield    schedule 26.11.2012
comment
в качестве теста я изменил свою строку sendto на if((TOTAL_BYTES_SEND += sockto(sock,...)) == -1) { printf("error"); }, и ошибок нет, и теперь я получаю реальный вывод функции sendto(), но на сервере, которым я владею, с загрузкой 4000 КБ/с, я все еще получаю очень низкие истекшие байты, что вызывает SLEEP становится 0, что не увеличивает количество истекших байтов, как на моем сервере (режим сна регулируется вверх и вниз, чтобы поддерживать кбит/с, рассчитанный как maxthrottle)   -  person randy newfield    schedule 26.11.2012
comment
Вы должны разделить прошедшие байты на время, прошедшее с момента последнего измерения. В противном случае вы получите некоторое случайное количество отправленных байтов (в пределах неопределенного интервала), которое напрямую не сопоставимо с maxthrottle (в кбит/с). Кроме того, этот код работает в условиях гонки. Вы должны использовать мьютекс для синхронизации доступа к общим переменным.   -  person Alexey Feldgendler    schedule 26.11.2012


Ответы (2)


Кажется, что это может быть самым прямым образом решено путем вычисления времени сна дроссельной заслонки в потоке отправки. Я не уверен, что вижу преимущество другого потока для выполнения этой работы.

Вот один из способов сделать это:

Выберите временное окно, в котором вы будете измерять скорость отправки. В зависимости от вашей целевой пропускной способности это даст вам максимум байтов за это время. Затем вы можете проверить, отправили ли вы столько байтов после каждого sendto(). Если вы превысите пороговое значение в байтах, спите до конца окна, чтобы выполнить регулирование.

Вот непроверенный код, демонстрирующий эту идею. Извините, что clock_gettime и struct timespec добавляют сложности. У Google есть несколько хороших фрагментов кода для более полных сравнений, сложений и вычитаний с помощью struct timespec.

#define MAX_BYTES_PER_SECOND (128L * 1024L)
#define TIME_WINDOW_MS 50L
#define MAX_BYTES_PER_WINDOW ((MAX_BYTES_PER_SECOND * TIME_WINDOW_MS) / 1000L)

#include <time.h>
#include <stdlib.h>

int foo(void) {
  struct timespec window_start_time;

  size_t bytes_sent_in_window = 0;
  clock_gettime(CLOCK_REALTIME, &window_start_time);

  while (1) {
    size_t bytes_sent = sendto(sock, buff, strlen(buff), 0, (struct sockaddr *) &sin, sizeof(sin));
    if (bytes_sent < 0) {
      // error handling
    } else {
      bytes_sent_in_window += bytes_sent;

      if (bytes_sent_in_window >= MAX_BYTES_PER_WINDOW) {
        struct timespec now;
        struct timespec thresh;

        // Calculate the end of the window
        thresh.tv_sec = window_start_time.tv_sec;
        thresh.tv_nsec = window_start_time.tv_nsec;
        thresh.tv_nsec += TIME_WINDOW_MS * 1000000;
        if (thresh.tv_nsec > 1000000000L) {
          thresh.tv_sec += 1;
          thresh.tv_nsec -= 1000000000L;
        }

        // get the current time
        clock_gettime(CLOCK_REALTIME, &now);

        // if we have not gotten to the end of the window yet
        if (now.tv_sec < thresh.tv_sec ||
            (now.tv_sec == thresh.tv_sec && now.tv_nsec < thresh.tv_nsec)) {

          struct timespec remaining;

          // calculate the time remaining in the window
          //  - See google for more complete timespec subtract algorithm
          remaining.tv_sec = thresh.tv_sec - now.tv_sec;
          if (thresh.tv_nsec >= now.tv_nsec) {
            remaining.tv_nsec = thresh.tv_nsec - now.tv_nsec;
          } else {
            remaining.tv_nsec = 1000000000L + thresh.tv_nsec - now.tv_nsec;
            remaining.tv_sec -= 1;
          }

          // Sleep to end of window
          nanosleep(&remaining, NULL);
        }

        // Reset counters and timestamp for next window
        bytes_sent_in_window = 0;
        clock_gettime(CLOCK_REALTIME, &window_start_time);
      }
    }
  }
}
person Ben Kelly    schedule 26.11.2012
comment
Глупый вопрос от моего имени, но что мне нужно изменить, чтобы заставить его регулироваться в зависимости от того, сколько % от общей пропускной способности я хочу использовать? id предполагает, что TIME_WINDOW_MS является переменной, но я не совсем понимаю, как я могу изменить ее, чтобы делать то, что мне нужно. также является ли библиотека компоновщика -lrt стандартной для большинства Linux-машин? если нет, смогу ли я вместо этого использовать struct timeval? - person randy newfield; 26.11.2012
comment
большое спасибо. этот метод фактически снижает пропускную способность. я просто не знаю, как преобразовать TIME_WINDOW_MS во время, которое напоминает процент от общей разрешенной пропускной способности. - person randy newfield; 26.11.2012
comment
я не могу отредактировать свои последние два комментария, но я попытался отредактировать MAX_BYTES_PER_SECOND, чтобы приравнять к ограниченной скорости полосы пропускания mbps = ((60 * 10) / 100) * 1024, которая должна равняться 6 кбит / с или 6144 байтам, но, похоже, это вообще не ограничивает соединение. что, кажется, ограничивает соединение, так это изменение значения TIME_WINDOW_MS и увеличение его где-то до 1 секунды (1000000). мне нужно создать какой-то вид, если усиление для переменной TIME_WINDOW_MS, чтобы увеличить его до тех пор, пока отправляемые байты не сравняются с моей предполагаемой ограниченной пропускной способностью, или должно быть достаточно изменить максимальное количество байтов на окно? - person randy newfield; 26.11.2012
comment
Извините за задержку с ответом. MAX_BYTES_PER_SECOND — целевая пропускная способность в абсолютном выражении. В коде я разместил его 128 КБ. Чтобы сделать процент от доступной пропускной способности, вы должны определить свой максимум. Код никак не может обнаружить это. Затем вы можете взять отношение цели к максимуму, чтобы определить процент. Конечно, вы можете изменить переменные, чтобы установить процент и максимум, чтобы затем рассчитать цель. Итак, что-то вроде #define MAX_BYTES_PER_SECOND (TARGET_PERCENT * TOTAL_AVAIL_BYTES_PER_SECOND). - person Ben Kelly; 26.11.2012
comment
Также обратите внимание, что у меня была ошибка в том, как я рассчитывал время окончания окна. Я умножал на 1000 вместо 1000000, чтобы преобразовать миллисекунды в наносекунды. Я не знаю, уловили ли вы это, но это немного отбросило бы алгоритм. Надеюсь, это поможет. - person Ben Kelly; 26.11.2012
comment
я исправил ту ошибку, которую вы упомянули о времени окончания окна. что я испытываю, так это то, что если я оставлю time_window_ms на 50, независимо от того, что я настрою max_bytes_per_second, он всегда отправляет больше данных, чем должен. чтобы попытаться исправить это, я установил time_window_ms на 1000 (1 секунду), а затем включил оператор отладки над bytes_sent_in_window = 0, чтобы распечатать эту переменную. после изменения он отправляет правильное количество максимальных байтов в секунду на окно (каждое окно составляет 1 секунду). это один из способов исправить этот код или здесь есть более глубокая проблема? - person randy newfield; 27.11.2012
comment
Сколько ты собираешься? Несколько байтов или больше? Если это всего несколько байтов, вы можете переместить проверку превышения MAX_BYTES_PER_SECOND до sendto(), а не после. Вам просто нужно предварительно рассчитать ожидаемое количество отправленных байтов вместо использования возвращаемого значения из sendto(). - person Ben Kelly; 27.11.2012
comment
Кроме того, какие другие значения TIME_WINDOW_MS вы пробовали? Имейте в виду, что хотя clock_gettime() и nanosleep() любят говорить о точности в наносекундах, вы не получите этого от операционной системы. 50 мс могут быть слишком малы для вашей системы. Я думаю, что 250 мс должны дать достаточно тиков на большинстве современных платформ. Размер окна на самом деле является настраиваемым параметром, который вам нужно выбирать в зависимости от того, насколько точным вы должны быть и насколько резким является трафик. С большим окном вы получите лучшую целевую пропускную способность в целом, но будет тенденция к резкому увеличению трафика, а затем к паузе и т. д. - person Ben Kelly; 27.11.2012
comment
с max_bytes_per second, равным 54000 (54 КБ/с), и time_window_ms, равным 50, до сброса bytes_sent_in_window оно равняется 1080040. изменение time_window_ms на 250 bytes_sent_in_window равняется 216060 до его сброса. изменение time_window_ms на 500 делает bytes_sent_in_window 108030. изменение time_window_ms на 1000 bytes_sent_in_window становится 54015, то есть целевыми байтами, которые я хочу использовать в секунду. - person randy newfield; 27.11.2012
comment
Ой, извини! У меня есть еще одна глупая ошибка преобразования времени. Макрос MAX_BYTES_PER_WINDOW должен использовать TIME_WINDOW_MS / 1000 вместо 1000 / TIME_WINDOW_MS. Я обновлю код выше, чтобы отразить. Обратите внимание: вы можете получить некоторые странные ошибки усечения, поскольку мы занимаемся целочисленной математикой. Вы можете переключиться на использование с плавающей запятой, если это вызывает проблемы. - person Ben Kelly; 27.11.2012
comment
Большое спасибо за рабочее решение. Вы мне очень помогли. Теперь все работает как задумано. - person randy newfield; 27.11.2012

Если вы хотите сделать это на уровне приложения, вы можете использовать такую ​​утилиту, как просачиваться, чтобы ограничить или изменить скорость передачи сокетов, доступную приложению.

Например,

trickle -s -d 50 -w 100 firefox

запустит firefox с максимальной скоростью загрузки 50 КБ/с и пиковым окном обнаружения 100 КБ. Изменение этих значений может дать что-то подходящее для тестирования вашего приложения.

person Richard    schedule 26.11.2012
comment
Я ненавижу устанавливать Trickle на каждую машину, на которой я хочу запустить свое приложение, поэтому я пытаюсь сделать это программно. хотя это может быть полезно для базового тестирования, чтобы увидеть, соответствует ли мой дроссель дроссельной заслонке. Спасибо за это. - person randy newfield; 27.11.2012