Prueba gratis
Engineering

Tiempo transcurrido con Ruby, de la manera correcta

Luca Guidi's profile picture Luca Guidi on

Cuando quieres calcular el tiempo transcurrido con Ruby, ¿qué sueles hacer?

comenzando = Hora.ahora
# operación que requiere mucho tiempo
final = Tiempo.ahora
transcurrido = finalizando - comenzando
transcurrido # => 10.822178

⚠ Esto está mal. Veamos por qué.

El tiempo no avanza solo hacia delante

Dependiendo de la configuración del sistema operativo (SO) de bajo nivel, Time.now de Ruby [usa] (https://github.com/ruby/ruby/blob/96db72ce38b27799dd8e80ca00696e41234db6ba/time.c#L1749) gettimeofday o clock_gettime Funciones de Linux de time.h. Según la documentación de gettimeofday:

[It] da el número de segundos y microsegundos desde la Época. Devuelve una estructura con el número de segundos y la zona horaria del sistema. Ruby VM luego puede calcular y devuelve un objeto Tiempo con esta información. Esto a menudo se indica como wall time en la documentación de Linux. Sin embargo: La hora devuelta por gettimeofday() se ve afectada por saltos discontinuos en la hora del sistema (por ejemplo, si el administrador del sistema cambia manualmente la hora del sistema). Esto no solo se ve afectado por los ajustes manuales, sino también por las reconciliaciones automáticas del reloj del sistema. Por ejemplo, si su servidor usa NTP: Idealmente, la hora de referencia es la misma en todo el mundo. Una vez sincronizado, no debería haber cambios inesperados entre el reloj del sistema operativo y el reloj de referencia. Por lo tanto, NTP no tiene métodos especiales para manejar la situación. Para un pequeño desplazamiento ntpd ajustará el reloj local como de costumbre. En la documentación de NTP, hay una sección completa sobre [calidad de los relojes] (http://www.ntp.org/ntpfaq/NTP-s-sw-clocks-quality.htm): Desafortunadamente, todo el hardware de reloj común no es muy preciso. Esto se debe simplemente a que la frecuencia que hace que el tiempo aumente nunca es exactamente la correcta. Incluso un error de solo 0.001% haría que el reloj se atrasara casi un segundo por día. La falta de precisión perfecta se debe a las condiciones físicas de la CPU, como la temperatura, la presión del aire e incluso los campos magnéticos. Entonces, cuando el sistema operativo intenta establecer una nueva hora del sistema, no garantiza que el nuevo valor sea en el futuro. Si una CPU tiene un reloj que es "demasiado rápido", el sistema operativo puede decidir restablecer el tiempo unos segundos hacia atrás. Otra razón por la que los relojes de pared tienen fallas es porque algunas CPU no pueden administrar los segundos bisiestos. En Wikipedia hay una página dedicada a incidentes en el historial del software debido a segundos bisiestos. 🤓 En resumen: el reloj del sistema flota constantemente y no solo avanza. Si su cálculo del tiempo transcurrido se basa en él, es muy probable que se encuentre con errores de cálculo o incluso interrupciones.

Tiempo transcurrido, de la manera correcta

¿Cómo puede calcular el tiempo transcurrido que tiene incrementos constantes que solo avanzan? 🤔 Los sistemas Posix han resuelto este problema introduciendo un reloj monotónico. Es conceptualmente similar a un temporizador que comienza con un evento y no se ve afectado por problemas de tiempo flotante. Cada vez que solicitas la hora al reloj monotonic, te devuelve la hora desde ese evento. En Mac OS, este evento es el inicio del sistema. Junto con monotonic, hay varios tipos de reloj: realtime, monotonic raw, virtual solo por nombrar algunos. Cada uno de ellos resuelve un problema diferente. Desde Ruby 2.1+ (MRI 2.1+ y JRuby 9.0.0.0+) existe un nuevo método que permite acceder a los valores actuales de todos estos relojes: Process.clock_gettime. Esto lleva el nombre de la función de Linux clock_gettime (todavía de time.h).

t = Proceso.clock_gettime(Proceso::CLOCK_MONOTONIC)
# => 2810266.714992
t / (24 * 60 * 60.0) # tiempo / días
# => 32.52623512722222

La variable t expresa el tiempo en segundos desde el arranque del sistema.

$ tiempo de actividad
14:32 hasta 32 días, 12:29, 2 usuarios, promedios de carga: 1.70 1.74 1.67

¿Cómo medir el tiempo transcurrido con Ruby entonces?

comenzando = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# operación que requiere mucho tiempo
final = Proceso.clock_gettime(Proceso::CLOCK_MONOTONIC)
transcurrido = finalizando - comenzando
transcurrido # => 9.183449000120163 segundos

Conclusión

Si observa el ecosistema de Ruby, hay literalmente [miles de casos] (https://github.com/search?utf8=%E2%9C%93&q=elapsed+time.now+language%3ARuby&type=Code) donde el los cálculos del tiempo transcurrido son incorrectos. Tal vez este sea un buen momento para que contribuyas al Open Source 💚💎 y soluciones estos problemas. 😉 Recuerda: reloj de pared es para decir la hora, reloj monotónico es para medir el tiempo. ⏰

Share on Twitter and Facebook

Luca Guidi's profile picture

Luca Guidi

Former astronaut, soccer player, superhero. All at the age of 10. For some reason now I write code.

We think domain management should be easy.
That's why we continue building DNSimple.

Try us free for 30 days
4.5 stars

4.3 out of 5 stars.

Based on Trustpilot.com and G2.com reviews.