Рассчитать частоту событий в секунду - PullRequest
0 голосов
/ 07 января 2019

У меня есть файл игры с миллионами событий, размер файла может быть> 10 ГБ

Каждая строка представляет собой игровое действие, например:

player 1, action=kill, timestamp=xxxx(ms granularity)
player 1, action=jump, timestamp=xxxx
player 2, action=fire, timestamp=xxxx

Каждое действие является уникальным и конечным для этого набора данных.

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

Мой план в полупсевдокоде:

lastReadGameEventTime = DateTime.MinValue;

while(line=getNextLine() != null)
{
   parse_values(lastReadGameEventTime, out var timestamp, out var action);
   if(timestamp ==  MinValue)
   {
      lastReadGameEventTime = timestamp;
   }
   else if(timestamp.subtract(lastReadGameEventTime).TotalSeconds > 1)
   {
      notify_points_for_this_second(datapoints);
      datapoints = new T();
   }

   if(!datapoints.TryGetValue(action, out var act))
      act = new Dictionary<string,int>();
      act[action] = 0;
   else
      act[action]++;
}
  lastReadGameEventTime = parse_time(line)

Меня беспокоит, что это слишком наивно. Я думал, может быть, посчитать всю минуту и ​​получить среднее значение в секунду. Но, конечно, я буду скучать по шипам игровых событий. И если я хочу рассчитать среднее значение за 5 дней, это еще больше ухудшит набор результатов. Какие-нибудь умные идеи?

1 Ответ

0 голосов
/ 07 января 2019

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

86 400 секунд в день. Допустим, вы хотите получить информацию за 10 дней. Вы можете создать кольцевой буфер размером 864 000 для хранения счетчиков за 10 дней:

const int SecondsPerDay = 86400;
const int TenDays = 10 * SecondsPerDay;

int[] TenDaysEvents = new int[TenDays];

То есть у вас всегда есть последние 10 дней.

Предполагая, что у вас есть обработчик событий, который читает данные вашего сокета и передает информацию в функцию, вы можете легко обновлять свои данные:

DateTime lastEventTime = DateTime.MinValue;
int lastTimeIndex = 0;

void ProcessReceivedEvent(string event)
{
    // here, parse the event string to get the DateTime
    DateTime eventTime = GetEventDate(event);
    if (lastEventTime == DateTime.MinValue)
    {
        lastTimeIndex = 0;
    }
    else if (eventTime != lastEventTime)
    {
        // get number of seconds since last event
        var elapsedTime = eventTime - lastEventTime;
        var elapsedSeconds = (int)elapsedTime.TotalSeconds;

        // For each of those seconds, set the number of events to 0
        for (int i = 1; i <= elapsedSeconds; ++i)
        {
            lastTimeIndex = (lastTimeIndex + 1) % TenDays; // wrap around if we get past the end
            TenDaysEvents[lastTimeIndex] = 0;
        }
    }
    // Now increment the count for the current time index
    ++TenDaysEvents[lastTimeIndex];
}

Это сохраняет в памяти последние 10 дней и легко обновляется. Отчетность немного сложнее, потому что начало может быть в середине массива. То есть, если текущий индекс равен 469301, то время запуска составляет 469302. Это кольцевой буфер. Наивным способом сообщить об этом будет скопировать циклический буфер в другой массив или список с начальной точкой в ​​позиции 0 в новой коллекции, а затем сообщить об этом. Или вы можете написать собственный перечислитель, который отсчитывает от текущей позиции и начинает там. Это не было бы особенно сложно создать.

Прелесть вышеописанного в том, что ваш массив остается статичным. Вы выделяете его один раз и просто используете повторно. Возможно, вы захотите добавить еще 60 записей, чтобы между текущим временем и временем 10 дней назад был некоторый «буфер». Это предотвратит изменение данных за 10 дней назад во время запроса. Добавьте еще 300 предметов, чтобы получить 5-минутный буфер.

Другой вариант - создать связанный список записей. Опять один раз в секунду. При этом вы добавляете элементы в конец списка и удаляете более старые элементы спереди. Всякий раз, когда событие приходит в течение новой секунды, добавьте запись о событии в конец списка, а затем удалите записи, которые находятся более чем на 10 дней (или независимо от вашего порога), из начала списка. Вы все еще можете использовать LINQ, чтобы сообщать о вещах, как рекомендуется в другом ответе.

Вы тоже можете использовать гибрид. По мере прохождения каждой секунды записывайте запись в базу данных и сохраняйте в памяти последнюю минуту, или час, или что-то еще. Таким образом, у вас есть самые свежие данные, доступные в памяти для быстрых отчетов и обновлений в реальном времени, но вы также можете использовать базу данных для составления отчетов за любой период с момента первого сбора данных.

Что бы вы ни решили, вам, вероятно, следует иметь какую-то базу данных, потому что вы не можете гарантировать, что ваша система не выйдет из строя. Фактически, вы можете в значительной степени гарантировать, что ваша система выйдет из строя в какой-то момент. Потеря данных или необходимость сканировать терабайты данных журналов - это не весело, чтобы заново собрать данные, которые вы собрали с течением времени.

...