Статьи

Как обнаружить и диагностировать медленный код в производстве

Одна из наиболее сложных задач, стоящих перед разработчиком, — это поиск и диагностика медленно работающего кода в рабочей среде.

Во-первых, как вы контролируете свой производственный код, не замедляя его? Конечно, вы не можете запустить производственный код через профилировщик. Даже если у вас есть механизм для синхронизации вашего кода, как тогда вы диагностируете проблему? Это особенно актуально, если вы не можете воспроизвести проблему в среде разработки. В идеале вы хотите получать уведомления о возникновении проблемы на производстве и получать достаточное количество информации, чтобы иметь разумную возможность исправить или хотя бы диагностировать проблему.

Вот механизм, предложенный моим коллегой Питером Лори, который вы можете использовать именно для этого. (Полный список кодов можно найти здесь ).

Что вы делаете, это создаете класс Monitor, как показано ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Monitor implements Runnable{
  private final Thread thread;
  private final AtomicLong startTime
                  = new AtomicLong(Long.MAX_VALUE);
  private final int thresholdMS;
 
  public Monitor(Thread thread, int thresholdMS){
    this.thread = thread;
    this.thresholdMS = thresholdMS;
  }
 
  public void reset(){
    startTime.set(System.currentTimeMillis());
  }
 
  @Override
  public void run(){
    while(thread.isAlive()){
      long timeTaken = System.currentTimeMillis()-startTime.get();
      if(timeTaken > thresholdMS){
        System.out.println(timeTaken + "-------------------------");
        Stream.of(thread.getStackTrace())
              .forEach(System.out::println);
      }
      try {
        Thread.sleep(thresholdMS/2);
      } catch (InterruptedException e) {
        break;
      }
    }
  }
}

Этот класс сбросит трассировку стека работающего потока, если поток не сможет выполнить сброс в течение порогового времени.

Вот пример программы, демонстрирующей, как будет называться Monitor.

01
02
03
04
05
06
07
08
09
10
11
12
13
Monitor monitor = new Monitor(Thread.currentThread(), 8);
Thread thread = new Thread(monitor, "MonitorThread");
thread.setDaemon(true);
thread.start();
 
while(true) {
   monitor.reset();
   double x=0;
   for (int i = 0; i < 10_000; i++) {
     x += Math.sqrt(i);
     Logger.getLogger(getClass().getName()).fine("x=" + x);
   }
}

Этот «критический» фрагмент кода отслеживается Monitor . Он сбросит трассировку стека кода, если монитор не будет сброшен в течение 8 мс.

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

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