Weblogs Código

Mascando Bits

Cómo revisar y probar un Pull Request (PR) en GitHub, GitLab o Gogs

febrero 27, 2020 08:00

Hay gente que a día de hoy sigue sin entender la diferencia entre Git (herramienta de versionado) y GitHub (plataforma para alojar proyectos Git, ahora propiedad de Microsoft). Y mucho menos entiende el valor que aportan plataformas web como son GitHub, GitLab o Gogs, que añaden todo el valor social y de discusión sobre los cambios propuestos por otros desarrolladores o usuarios, permitiendo así el Desarrollo Colaborativo y auge que hoy en día presenta la filosofía de software Open Source.

Siendo así, vamos a aclarar que Git es una estupenda herramienta de versionado y sincronización de código, o cualquier fichero que no sea binario, permitiendo establecer la sucesión cronológica y relacionar los cambios entre sí para una control y revisión del avance de un proyecto.

Pero por otro lado Git solo, es bastante «cojo» por así decirlo. Las personas no nos relacionamos y trabajamos mediante protocolos o herramientas que llevamos instaladas y que nos permitan trabajar en equipo de manera eficaz 🤣. Es aquí donde entran las plataformas web que dan esa capa social que permiten a los desarrolladores y contribuidores al proyecto comunicarse, establecer criterios, marcar pautas, organizarse, revisar secciones de código… En este artículo nos vamos a centrar en el proceso de revisión de aportes de código Pull Request, abreviados y conocidos como PR.

Esquema básico del proceso de revisión:

Antes de empezar vamos a aclarar una serie de términos que creo que es conveniente definir. Lo primero explicar lo que es un Fork, que no es más que un clon de un proyecto cuya propiedad (en términos de administración) es de otro usuario u otra organización, pero permanece relacionado con su proyecto originario del que se hizo fork (normalmente se denomina al proyecto origen upstream). Y luego lo que es un Pull Request (de ahora en adelante PR), que es una petición que se hace al proyecto originario (upstream) para que incluya los cambios que tenemos en una de las ramas de nuestro repositorio «forkeado».

Entre el PR y la aceptación del mismo está lo que se conoce como discusión y revisión de los cambios propuestos. Los procesos de revisión son importantes porque permiten afinar los cambios, corregir metodologías de trabajo, realizar pruebas de integración con plataformas como AppVeyor o TravisCI… Podríamos decir que es el punto en el que se comunican las personas involucradas en la propuesta de PR.

Entrando en el terrenos práctico la discusión se genera en la sección de PR y es donde se produce toda la conversación. Aquí podéis ver un ejemplo:

https://github.com/RDCH106/pycoinmon/pull/5

Creo bastante evidente como se debate, ya que es como un foro en el que dependiendo de la plataforma (GitHub, GitLab o Gogs…) será más completo o menos.

La pregunta del millón es cómo probamos el código que está en el PR. No se encuentra en una rama del repositorio el PR, realmente se encuentra en la rama del que nos hace el PR (a donde podemos ir para probar los cambios). De hecho lo interesante es que el PR puede ir cambiando, si el autor del PR en su fork, en la rama que usó para el PR, realiza cambios. Es decir, un PR no es un elemento estático sujeto a un commit concreto, sino que es la referencia a una rama del proyecto fork que contiene cambios que el upstream no tiene. El truco reside en que comparten la misma base de código y los cambios del PR son la diferencia entre la rama del proyecto upstream al que se le propone el PR y la rama del proyecto fork que contiene los cambios.

Pero esto es una verdad a medias… y digo a medias porque un PR sí es una rama, realmente es una referencia en el repositorio remoto (donde hacemos pull y push). Y eso es bueno 😁. La referencia entre un fork y su upstream nunca se rompe de manera oficial (existen por ejemplo mecanismos en GitHub para solicitar que un fork de un proyecto deje de estar relacionado con su upstream), por lo que asumiremos que un fork es como un padre y un hijo, pueden no llevarse bien pero eso nunca hará que desaparezca la relación parental padre-hijo. Cada vez que la rama del fork desde la que se hace PR se actualiza, la del proyecto upstream también queda actualizada. Por eso resulta conveniente usar ramas para hacer PR de diferentes features (características) que vamos a desarrollar, ya que así podremos mantener de manera independiente ambos paquetes de trabajo sin que haya interferencias entre ellos, y pudiendo dejar la rama el tiempo que haga falta hasta que finalmente se incluyan los cambios al proyecto upstream.

A causa de lo comentado antes podemos revisar desde la consola las referencias remotas con las que cuenta un proyecto git con el comando «git ls-remote«. Voy a usar el proyecto pyCOINMON como ejemplo totalmente práctico:

rdch106@RDCH106 MINGW64 /d/GIT/pycoinmon (develop)
$ git ls-remote
From https://github.com/RDCH106/pycoinmon.git
be75cbb0e449383fd42c9033a8b76c844fcfef17        HEAD
d01ecf133c342f490030b56066ed8ff18b36f124        refs/heads/develop
be75cbb0e449383fd42c9033a8b76c844fcfef17        refs/heads/master
ec9c10b6d88274a1a006ae139856de478876572c        refs/pull/1/head
c0cb91e1776632494a23cf181584c1903516a758        refs/pull/11/head
46f2e6db3d22a4a48dcf3680157c95641123095d        refs/pull/2/head
035e64652c533a569c6b236f54e12aff35ad82b1        refs/pull/3/head
0f08bd644912e7de4a3bc53e3a8d0dbd64d6fc34        refs/pull/4/head
87ef8ca9af3eb5a523d3ef532fccee91aeedcd44        refs/pull/5/head
896d697e846649c7f23b9af3430067451ab5c089        refs/pull/7/head
d57455a8acf719cd3acea623f0759c6e11baada1        refs/pull/7/merge
d6f4c5a4b3e2b0025d74e9feed2609ba50835950        refs/tags/v0.1.0
be413f410cfa23e1f0f2715f2714874a22757b0d        refs/tags/v0.1.0^{}
cdc5e356b6d7f829e83a1b0a25e2a7304844223b        refs/tags/v0.1.4
4b69c059196f1f41bafc4f5970afb8a34817b509        refs/tags/v0.1.4^{}
b5649af39db2738481751f07b6ad59585144dbd0        refs/tags/v0.2.4
c3506ed1289f3dbcddb2db68201a114a9d1cf8b3        refs/tags/v0.2.4^{}
f0c564d2617d42c0fcb3cb842122a214a6021d37        refs/tags/v0.2.6
a3fe8f8b788311c1d4f5ee906cf2a4c70ffac8db        refs/tags/v0.2.6^{}
cea535e4e663531454b437ee8d7976a06e1ae415        refs/tags/v0.2.8
080192d1d7eb33feed6b92df2b389b39592545a6        refs/tags/v0.2.8^{}
9c374d2031a9da655be44e0c79944166cbf99e8a        refs/tags/v0.3.2
f76f78252863d372fde5d6ff197ba7519f7da50b        refs/tags/v0.3.2^{}
cc248bbd64f177a9b6041cad7c269b880c21b355        refs/tags/v0.3.5
fd50d344575e54def4b841221b2b5c2e8a0a74e3        refs/tags/v0.3.5^{}
7727986a93ce65c2deb905fcd92718c43f3e61ca        refs/tags/v0.4.0
aa2df260dc9c1ae9c1aa88ddd7d2498b451bf0fc        refs/tags/v0.4.0^{}
a620d5e379d495c69926d97a33803e91b3c07783        refs/tags/v0.4.4
d0b3fa9ae73a15ff17a47804fbbac80245c028c1        refs/tags/v0.4.4^{}
f12102dfacc530f90600082a97a0533ba862bd2c        refs/tags/v0.4.7
4329b9f5a9782071fe661376225df6d8d6dfa47e        refs/tags/v0.4.7^{}
1d3dbf367ab319f0d9a515c007ff07b90de6ca1c        refs/tags/v0.4.8
d01ecf133c342f490030b56066ed8ff18b36f124        refs/tags/v0.4.8^{}

Nos vamos a centrar por ejemplo en el PR #7 que es uno de los que tenemos abiertos y cuyos cambios supongamos que queremos probar:

Lo que vamos a hacer es traernos los cambios del PR basándonos en su ID (en nuestro caso #7), creando una nueva rama (PR_7) para poder probarlo por separado:

rdch106@RDCH106 MINGW64 /d/GIT/pycoinmon (develop)
$ git fetch origin pull/7/head:PR_7
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Total 10 (delta 7), reused 7 (delta 7), pack-reused 3
Unpacking objects: 100% (10/10), done.
From https://github.com/RDCH106/pycoinmon
 * [new ref]         refs/pull/7/head -> PR_7

Ahora sólo resta hacer un checkout a PR_7:

rdch106@RDCH106 MINGW64 /d/GIT/pycoinmon (develop)
$ git checkout PR_7
Switched to branch 'PR_7'

O hacerlo desde el maravilloso SourceTree el cual os recomiendo como herramienta profesional para la gestión de vuestros repositorios Git locales y remotos (si eres usuario GNU/Linux no me olvido de ti, te recomiendo GitKraken).

A partir de aquí ya es hacer lo que quieras. Puedes probar los cambios, hacer algunas pruebas, hacer un «merge» para incluir los cambios en otra rama o incluso en la propia rama master.

En lo que a mí respecta, recomiendo simplemente probar los cambios y en la zona de discusión que comentaba antes, realizar todas las apreciaciones necesarias y dejar las cosas lo más claras posible, siendo responsabilidad del desarrollador que hizo el PR realizar los cambios para evitar conflictos. Una vez esté todo correcto, también recomiendo realizar el merge desde GitHub, GitLab o Gogs

Además todo este proceso forma parte del aprendizaje y democratización de los conocimientos, haciendo de cada PR un sitio al que se puede volver para aprender y revisar lo ocurrido gracias a la trazabilidad en la discusión de los cambios.

Personalmente los procesos de revisión es una base estupenda junto al Planning Poker (prueba https://scrumpoker.online), para igualar las habilidades y competencias que permiten acortar divergencias entre las estimaciones de un grupo de desarrollo ante la planificación de las cargas de trabajo. Yo personalmente soy partidario de que el que lanzó la estimación más alta en la partida de poker realice el paquete de trabajo para que baje su estimación y aumenten sus habilidades, gracias a la supervisión (revisión) de quien dio la cifra más baja, que se asume que dio una estimación más ajustada por unas habilidades mayores.

» Leer más, comentarios, etc...

Bitácora de Javier Gutiérrez Chamorro (Guti)

Evitar el uso de un plugin SMTP en WordPress

febrero 25, 2020 02:24



Llevo mucho sin escribir sobre informática en este espacio. Como si los relojes se lo hubieran comido todo. En este artículo os voy a explicar como prescindir de plugins SMTP de WordPress. Son nombres que si usas WordPress conocerás: Easy WP SMTP, WP Mail SMTP, WP SMTP, … El problema con los plugins de WordPress …

Evitar el uso de un plugin SMTP en WordPress Leer más »



Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

» Leer más, comentarios, etc...

Fixed Buffer

Cómo escribir un logger de alto rendimiento para .Net Core

febrero 25, 2020 09:00

Tiempo de lectura: 6 minutos
Imagen ornamental para el entrada Cómo escribir un logger de alto rendimiento para .Net Core

Han pasado ya unos días desde la última entrada en la que hablamos sobre como escribir código de alto rendimiento en .Net Core. Aunque en la entrada intenté meter todo lo posible, al final quedó bastante larga. Es por eso por lo que pensé que en vez de hablar por encima sobre como escribir un logger de alto rendimiento, era mejor dedicarle una entrada en exclusiva. ¡Vamos con ello!

¿Por qué debería escribir un logger de alto rendimiento?

Aun a riesgo de parecer pesado y repetir lo mismo que en la entrada anterior, el código de alto rendimiento es menos legible y más difícil de mantener que un código normal. Antes de decidir hacer modificaciones para conseguir un alto rendimiento, es importante medir. Con los datos de las mediciones en la mano, hay que evaluar si vale la pena cambiar el código para conseguir mejor rendimiento. (Sin medir, ni siquiera se puede afirmar de donde viene el problema…)

Si tienes dudas sobre cómo hacer esto, para aquí y lee Escribiendo código de alto rendimiento en .Net Core.

La interfaz ILogger está totalmente integrada en .Net Core tanto a nivel de web como de servicio, y es la piedra angular sobre la que se sustenta todo el sistema de logging. Esta tan bien integrado que no usarlo requiere que nos preguntemos si no estaremos reinventando la rueda….

Vale, tenemos mediciones de tiempo y podemos afirmar que hay que optimizar el logger para conseguir alto rendimiento. Tenemos totalmente integrado nuestro sistema con ILogger. ¿Dónde está el problema entonces?

Si hacemos una medición con un código como por ejemplo este:

[MemoryDiagnoser]
public class ILoggerParameters
{
    private readonly ILogger _logger;
    public ILoggerParameters()
    {
        IServiceCollection serviceCollection = new ServiceCollection();

        serviceCollection.AddLogging();

        var loggerFactory = serviceCollection.BuildServiceProvider().GetService<ILoggerFactory>();

        _logger = loggerFactory.CreateLogger("TEST");
    }

    [Benchmark(Baseline = true)]
    public void LoggerWith0Parameters()
    {
        _logger.LogInformation("Mensaje de log sin parámetros");
    }

    [Benchmark]
    public void LoggerWith1Parameters()
    {
        _logger.LogInformation("Mensaje de log con un parámetro {Parametro1}", "Valor1");
    }

    [Benchmark]
    public void LoggerWith2Parameters()
    {
        _logger.LogInformation("Mensaje de log con dos parámetros ({Parametro1},{Parametro2})", "Valor1", "Valor2");
    }
}

Podemos comprobar que los resultados apuntan a que el número de parámetros influye en el rendimiento del log.

La imagen muestra los resultados del benchmark del código, donde se ve que a medida que aumentan los parámetros el tiempo de ejecución aumenta 3.6 veces con un parámetro y 4.3 veces con 2 parámetros. También se refleja que sin parámetros no hay consumo de memoria, con 1 se consumen 32B y con 2 40B.

De hecho, hay un punto interesante, y es que incluso hay un consumo de memoria al utilizar parámetros pero… Si los parámetros son string y son por referencia, ¿porque hay consumo? ¿Es esto suficiente para justificar un logger de alto rendimiento?

¿Qué hay bajo el capo de ILogger?

En los datos anteriores hemos comprobado que cuando se utiliza ILogger con parámetros, el consumo de memoria aumenta pese a que sean datos por referencia.

Esto a simple vista puede parecer raro, pero tiene todo el sentido si entramos a detalle de como esta implementado ILogger, vamos a poder comprobar que a fin de simplificar la generalización se hacen ciertas conversiones que añaden coste computacional al proceso, como por ejemplo convertir los parámetros en un «params». Esto obliga a crear un array que hay que reservar. Encima de todo eso, «params» es de tipo object[], por lo que además tenemos un boxing por el camino.

Se conoce como boxing el proceso de hacer una conversión de cualquier tipo a object. El unboxing hace referencia al proceso contrario, convertir object a cualquier otro tipo.

De hecho, basta con mirar la firma de uno de los métodos de ILogger como puede ser LogInformation (aunque sea un método de extensión, el método Log recibe los mismos args).

public static void LogInformation(this ILogger logger, string message, params object[] args)

En él, ya se puede comprobar que se está haciendo un boxing desde el tipo de dato que le pasemos como argumento hacia object y se esta convirtiendo en un array.

En el caso de trabajar con valores por referencia, el boxing y unboxing no es un problema ya que todos los datos ya están en el heap (cómo bien indica Daniel Redondo en los comentarios). En cambio si estamos haciendo boxing de un objeto por valor, la cosa es diferente tanto en tiempo como en memoria. Un código como este:

[Benchmark(Baseline = true)]
public int Boxing() => Boxing(Message);
[Benchmark]
public int NoBoxing() => NoBoxing(Message);
private int Boxing(object message) => (int)message;
private int NoBoxing(int message) => message;

Genera un resultado tan interesante como este:

La imagen muestra el resultado del benchmark anterior. En el se ven que el boxing tarda 4.23 ns y consume 24 B mientras que no haciendo boxing el resultado es 0.01 ns y 0 B. El ratio sin hacer boxing es 99,995% más eficiente.

¿Cómo se puede conseguir un logger de alto rendimiento?

Por suerte, .Net core 3 ofrece un mecanismo para crear un logger de alto rendimiento a través de LoggerMessage.Define. ¿Y cómo funciona LoggerMessage.Define para mejorar el rendimiento?

Este mecanismo va a crear una plantilla que se compilará y servirá para un mensaje concreto. Esto es un trabajo extra (como todo cuando se optimiza), pero permite que las llamadas ejecuten esa plantilla en vez de interpolar strings. Además, al ser esa plantilla un ‘Action’ no es preciso realizar un boxing ya que los tipos son estrictos.

Adicionalmente a lo anterior, este Action debería ser estático de modo que no sea necesario recrearlo cuando la clase que contiene el logger se inicialice de nuevo.

Para ello, podríamos crear una clase abstracta que almacene todas esas plantillas (delegados) de nuestro logger de alto rendimiento:

public static class OptimizedLogger
{
    public static void LogInformation(ILogger logger) => _informationSinParametros(logger, null);
    public static void LogInformation(ILogger logger, string parametro1) => _information1Parametro(logger, parametro1, null);
    public static void LogInformation(ILogger logger, string parametro1, string parametro2) => _information2Parametros(logger, parametro1, parametro2, null);


    private static readonly Action<ILogger, Exception> _informationSinParametros = LoggerMessage.Define(
        LogLevel.Information,
        Events.Evento1,
        "Mensaje de log sin parámetros");

    private static readonly Action<ILogger, string, Exception> _information1Parametro = LoggerMessage.Define<string>(
        LogLevel.Information,
        Events.Evento1,
        "Mensaje de log con 1 parámetro: {Parametro1}");

    private static readonly Action<ILogger, string, string, Exception> _information2Parametros = LoggerMessage.Define<string, string>(
        LogLevel.Information,
        Events.Evento1,
        "Mensaje de log con 2 parámetros: {Parametro1} y {Parametro2}");
}

Haciendo un resumen del código, estamos creando 3 delegados tipados con LoggerMessage para cada uno de los 3 escenarios. Hemos conseguido añadir parámetros utilizando la sobrecarga genérica de LoggerMessage.Define y ahora tenemos 3 delegados que reciben un ILogger, una excepción y entre o y 2 strings.

¿Hemos conseguido algo con esto? ¿No estamos utilizando el mismo ILogger que tenía mal rendimiento? Pues sí y no. Sí estamos utilizando el mismo ILogger, pero no estamos llamando a su método Log, sino que estamos utilizando el delegado que hemos definido previamente de modo que ahora las llamadas son fuertemente tipadas.

Vamos a comprobar si realmente hemos mejorado con un código que compare los dos loggers en el caso de tener 2 parámetros como podría ser este:

[MemoryDiagnoser]
public class ILoggerVsOptimized2Params
{
    private readonly ILogger _logger;
    private const string Parametro1 = "Parametro1";
    private const string Parametro2 = "Parametro2";

    public ILoggerVsOptimized2Params()
    {
        IServiceCollection serviceCollection = new ServiceCollection();
        serviceCollection.AddLogging();
        var loggerFactory = serviceCollection.BuildServiceProvider().GetService<ILoggerFactory>();
        _logger = loggerFactory.CreateLogger("TEST");
    }

    [Benchmark(Baseline = true)]
    public void Logger2Parametros()
    {
        _logger.LogInformation("Mensaje de log con 2 parámetros: {Parametro1} y {Parametro2}", Parametro1, Parametro2);
    }

    [Benchmark]
    public void OptimizedLogger2Parametros()
    {
        OptimizedLogger.LogInformation(_logger, Parametro1, Parametro2);
    }
}

Si nos fijamos en los resultados podemos comprobar que este cambio mejora el rendimiento tardando un 93% menos y sin consumir nada de memoria.

La imagen muestra los resultados utilizando un logger de alto rendimiento. En el se ve que es un 93% más rápido y consume 0 B respecto a los 40 B del logger standard.

Conclusiones

Hacer un logger de alto rendimiento tiene un impacto a la hora de desarrollar el proyecto. Al igual que todas las optimizaciones es necesario que exista una razón cuantificable antes de hacer un desarrollo pensado para la máquina en vez de para las personas.

El hecho de tener que definir un delegado fuertemente tipado para cada mensaje que se vaya a loguear genera una sobrecarga de trabajo muy grande en un proyecto donde hay cientos de mensajes distintos. Es por eso que una opción muy interesante es valorar que logger tienen que ser de alto rendimiento (por ejemplo, en un middleware de cabecera de ASP) y que loggers pueden utilizar la implementación estándar.

Con esas consideraciones en la mano, evidentemente hacer los cambios tiene un beneficio importante en el coste de ejecución de la aplicación.

He dejado un repositorio en GitHub para que puedas descargarte el código y probarlo, tocarlo y cambiarlo para poder ver los resultados en tu propio equipo (que es como más se aprende).

**La entrada Cómo escribir un logger de alto rendimiento para .Net Core se publicó primero en Fixed Buffer.**

» Leer más, comentarios, etc...

Variable not found

Cambiar el idioma de mensajes del SDK de .NET Core

febrero 25, 2020 07:05

.NET Core Pues va un post cortito a modo de autorecordatorio, porque es algo que me ha pasado ya varias veces y siempre tengo que ponerme a investigar cómo solucionarlo.

Personalmente me gusta tener todos los entornos y herramientas de desarrollo en inglés, básicamente porque cuando encontramos problemas es más fácil encontrar soluciones si a la hora de buscar utilizamos los términos en este idioma... bueno, y de paso, evito ver algunas traducciones terribles ;)

Pues bien, en el caso del SDK de .NET Core, el idioma no es una característica que podamos elegir a la hora de instalarlo. Se instalarán todos los idiomas disponibles (podéis verlo por ejemplo en la carpeta %programfiles%\dotnet\sdk\3.1.101), y los mensajes se mostrarán en el idioma configurado por defecto en nuestra máquina. En mi equipo, por ejemplo, se muestra todo en idioma español:
C:\>dotnet xyz
No se pudo ejecutar porque no se encontró el comando o archivo especificados.
Algunas de las posibles causas son:
* Escribió mal un comando dotnet integrado.
* Intentó ejecutar un programa .NET Core, pero dotnet-xyz no existe.
* Tiene planeado ejecutar una herramienta global, pero no se encontró un ejecutable
con prefijo dotnet con este nombre en la RUTA DE ACCESO.

C:\>_

Sin embargo, existe una forma de forzar el uso de un idioma determinado en el SDK estableciendo la variable de entorno DOTNET_CLI_UI_LANGUAGE. Por ejemplo, para hacer que todo se muestre en inglés, podríamos hacer algo así:
C:\>set DOTNET_CLI_UI_LANGUAGE=en

C:\>dotnet xyz
Could not execute because the specified command or file was not found.
Possible reasons for this include:
* You misspelled a built-in dotnet command.
* You intended to execute a .NET Core program, but dotnet-info does not exist.
* You intended to run a global tool, but a dotnet-prefixed executable
with this name could not be found on the PATH.

C:\>_
Mi yo del futuro seguro agradecerá esta entrada. Y si de paso ayuda a alguien más, pues genial ;)

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 392

febrero 24, 2020 08:09

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin / Mobile

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Blog Bitix

Cómo migrar el contenido de un sitio web HTML a formato Markdown (de Wordpress a generador estático)

febrero 23, 2020 03:00

HTML

Markdown

Wordpress es una opción muy pupular por su facilidad de uso para crear sitios web y blogs. Sin embargo, tiene algunos puntos débiles como necesitar una base de datos para guardar el contenido lo que hace que su instalación, mantenimiento, actualizción o presente problemas de seguridad por necesitar de un procesamiento en este caso PHP para generar las páginas HTML a partir del contenido guardado en la base de datos y sus plugins instalados.

Por estos motivos los generadores de sitios web estáticos al ser mucho más simples son una alternativa, el resultado final es simplemente contenido HTML, hojas de estilo CSS, imágenes, JavaScript y los recursos adicionales que necesite. Todo este contenido estático simplemente necesita de un servidor web no necesita ninguna base de datos lo que lo hace más sencillo instalar en un servidor ni ningún programa que genere el contenido HTML. Además, los generadores de sitios web estáticos permiten editar el contenido en el ordenador local con cuales quiera herramientas instaladas, como Visual Studio Code, entre otras cosas para buscar y reemplazar u aplicar otros comandos de GNU/Linux para transformar el contenidode forma masiva.

Muchos de los generadores estáticos de sitios web como Hugo usan como formato de contenido [Markdown][markdonw]. Markdown utiliza una sintaxis que procesada se convierte con posterioridad a HTML, en los archivos Markdown también puede incluirse trozos de HTML que es emitido sin ningún cambio.

Migrar un sitio web web HTML generador por Wordpress u otro a un generador estático es posible, requiere las siguientes tareas:

  • Recuperar todo el contenido del sitio web original. El contenido complete de un sitio web se puede descargar con un comando de wget.
  • Extraer la parte de contenido propio de la página o del artículo. En Java con la librería jsoup, no es complicado utilizando un selector similar a los empleados por jQuery.
  • Convertir el contenido HTML a Markdown, en Java ofrecen esta funcionalidad las librerías remark y MarkdownJ.
1
$ wget -r -l0 https://picodotdev.github.io/blog-bitix/
wget.sh

Migrar un sitio web HTML con Wordpress sería una tarea que requeriría mucho tiempo, más si tiene muchos artículos, si se hace manualmente copiando y pegando el texto de cada artículo, imágenes, … Creando un script la mayor parte de la tarea se automatiza, seguramente hay que hacer algunas acciones manuales pero con _wget, _jsoup, remark y un script que no tiene por que ser muy largo en líneas de código la tarea es realizable con una inversión de tiempo razonable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html lang="es">
<head>
  <meta name="base" href="/blog-bitix/">
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="author" content="picodotdev">
  <meta name="keywords" content="java, programacion, software, hardware, tapestry, software libre, gnu, linux, gnu/linux, unix, arch, arch linux, html, css, javascript">
  <meta name="description" content="Para utilizar de forma efectiva y eficiente una distribución GNU/Linux es necesario conocer las tareas básicas que hay que realizar en todo sistema. Estas son actualizar los paquetes instalados del sistema a nuevas versiones con correcciones de seguridad, correcciones de errores y mejoras, instalar y desinstalar nuevos paquetes y programas. Conocer el uso básico de la terminal permite automatizar y realizar de forma masiva algunas tareas además de también permitir actualizar el sistema e instalar y desinstalar programas. &hellip;">

  <title>Tareas básicas de administración y uso después de instalar una distribución GNU/Linux</title>
  ...
  <article>
    <h1>Tareas básicas de administración y uso después de instalar una distribución GNU/Linux</h1>
    <p class="information">
        Escrito por <span>picodotdev</span> el <time>09/02/2020</time>.      
        <br>
        <a href="/blog-bitix/tags/gnu-linux/">gnu-linux</a>
        <a href="/blog-bitix/tags/planeta-codigo/">planeta-codigo</a>      
        <br>
        <a href="/blog-bitix/2020/02/tareas-basicas-de-administracion-y-uso-despues-de-instalar-una-distribucion-gnu-linux/">Enlace permanente</a>      
        <a href="/blog-bitix/2020/02/tareas-basicas-de-administracion-y-uso-despues-de-instalar-una-distribucion-gnu-linux/#comments">Comentarios</a>
    </p>
    <p class="summary">Para utilizar de forma efectiva y eficiente una distribución GNU/Linux es necesario conocer las tareas básicas que hay que realizar en todo sistema. Estas son actualizar los paquetes instalados del sistema a nuevas versiones con correcciones de seguridad, correcciones de errores y mejoras, instalar y desinstalar nuevos paquetes y programas. Conocer el uso básico de la terminal permite automatizar y realizar de forma masiva algunas tareas además de también permitir actualizar el sistema e instalar y desinstalar programas.</p>
    ...
    <p>Si has instalado recientemente o piensas instalar una distribución <a href="https://www.gnu.org/">GNU</a>/<a href="https://www.linux.com/">Linux</a> después de <a href="https://picodotdev.github.io/blog-bitix/2016/10/elegir-una-distribucion-gnu-linux-segun-el-usuario-uso-o-equipo/">elegir la distribución GNU/Linux</a> que más se adapte a tus preferecias y de seguir los pasos para <a href="https://picodotdev.github.io/blog-bitix/2017/05/descargar-e-instalar-la-distribucion-ubuntu-de-gnu-linux-paso-a-paso-desde-cero/">instalar una como Ubuntu</a>, después es necesario conocer unas pocas <a href="https://picodotdev.github.io/blog-bitix/2020/02/tareas-basicas-de-administracion-y-uso-despues-de-instalar-una-distribucion-gnu-linux/">tareas de administración del sistema</a>, <a href="https://picodotdev.github.io/blog-bitix/2020/02/las-aplicaciones-integradas-del-entorno-de-escritorio-gnome/">las aplicaciones del entorno de escritorio de GNOME</a> y un <em>listado de programas básicos según categoría en GNU/Linux</em>. En cada distribución varía ligeramente pero en todas hay que realizar unas tareas básicas de mantenimiento.</p>
    <p>Estas tareas básicas de mantenimiento son:</p>
    <ul>
    <li><strong>Actualizar los paquetes instalados del sistema</strong>. Los paquetes actualizados incluyen correcciones de seguridad por lo que es importante actualizar el sistema de forma regular. También, pueden incluir nuevas versiones de los paquetes con nuevas funcionalidades y correcciones de errores. Una programa que es necesario mantener actualizado es el navegador web, también el núcleo o <em>kernel</em> de Linux.</li>
    <li><strong>Instalar y desinstalar nuevos paquetes y programas</strong>. Dependiendo de las tareas que se deseen realizar hay que instalar los programas que permitan realizarlas. Para editar documentos ofimáticos, un navegador web, retocar imágenes, correo electrónico, descarga de <em>torrents</em>, reproductor de vídeo, reproductor de música, captura de imágenes, captura vídeo del escritorio, programas para el desarrollo y programación, virtualización, &hellip; Cada programa tienen su paquete en la ditribución que es necesario instalar para usarlo y desinstalar cuando el programa ya no se va a usar más. Es dificil que no encuentres un programa que realice lo que se desea.</li>
    <li><strong>Uso básico de la terminal</strong>. Hay programas con interfaz gráfica pero para algunas tareas es más rápido hacerlas desde la línea de comandos con la ventaja que con un <em>script</em> es posible automatizar en caso de ser repetitiva. Desde la línea de comandos hay numerosos programas útiles que además se pueden combinar de forma que la salida de uno sea la entrada de otro.</li>
    </ul>
    <p>Dependiendo de la distribución cada una de estas tareas puede variar el comando en concreto pero en general en todas se realizan de forma similar. A continuación comento como realizar las tareas en dos de las distribuciones más populares como son <a href="https://www.ubuntu.com/">Ubuntu</a> y <a href="https://www.archlinux.org/">Arch Linux</a> pero en <a href="https://fedoraproject.org/">Fedora</a>, <a href="https://www.debian.org/">Debian</a>, <a href="https://elementary.io/es/">elementary OS</a> se realizan de forma similar.</p>
    ...
page.xhtml

Descargado el sitio web la tarea del script es por cada artículo del sitio web aplicar un selector de jsoup extraer el contenido y convertirlo a Markdown con remark, también sería tarea del script mover los recursos a la ubicación que requiera el generador de sitios web estáticos del artículo como las imágenes, que el comando wget también descarga.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package io.github.picodotdev.blogbitix.sitemconverter;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import com.overzealous.remark.Remark;
import com.overzealous.remark.Options;

public class Main {

    public static void main(String[] args) throws Exception {
        Stream<Path> files = Files.walk(Paths.get("../picodotdev.github.io/"), 5);
        files.filter(path -> path.getFileName().toFile().getName().endsWith(".html") && path.getFileName()).forEach(path -> {
            try {
                System.out.println(path.toString());

                // Obtener el HTML de una página
                String html = Files.readString(path);

                // Contenido HTML
                System.out.println(html);

                // Parsear el contenido HTML con jsoup
                Document document = Jsoup.parse(html);

                // Obtener los elementos de contenido de la página con selectores
                Elements article = document.select("article");

                // Opciones para convertir a markdown
                Options options = Options.markdown();
                options.preserveRelativeLinks = true;
                options.inlineLinks = true;
                String baseUri = "https://picodotdev.github.io/blog-bitix/";

                // Convertir a markdown
                String markdown = new Remark(options).convertFragment(article.html(), baseUri);

                // Contenido markdown
                System.out.println(markdown);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}
Main.java

Resultado en formato markdown.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Tareas básicas de administración y uso después de instalar una distribución GNU/Linux #

Escrito por picodotdev el 09/02/2020.
[gnu-linux](/blog-bitix/tags/gnu-linux/) [planeta-codigo](/blog-bitix/tags/planeta-codigo/)  
[Enlace permanente](/blog-bitix/2020/02/tareas-basicas-de-administracion-y-uso-despues-de-instalar-una-distribucion-gnu-linux/) [Comentarios](/blog-bitix/2020/02/tareas-basicas-de-administracion-y-uso-despues-de-instalar-una-distribucion-gnu-linux/#comments)

Para utilizar de forma efectiva y eficiente una distribución GNU/Linux es necesario conocer las tareas básicas que hay que realizar en todo sistema. Estas son actualizar los paquetes instalados del sistema a nuevas versiones con correcciones de seguridad, correcciones de errores y mejoras, instalar y desinstalar nuevos paquetes y programas. Conocer el uso básico de la terminal permite automatizar y realizar de forma masiva algunas tareas además de también permitir actualizar el sistema e instalar y desinstalar programas.

![GNU]()

![Linux]()

Si has instalado recientemente o piensas instalar una distribución [GNU](https://www.gnu.org/)/[Linux](https://www.linux.com/) después de [elegir la distribución GNU/Linux](https://picodotdev.github.io/blog-bitix/2016/10/elegir-una-distribucion-gnu-linux-segun-el-usuario-uso-o-equipo/) que más se adapte a tus preferecias y de seguir los pasos para [instalar una como Ubuntu](https://picodotdev.github.io/blog-bitix/2017/05/descargar-e-instalar-la-distribucion-ubuntu-de-gnu-linux-paso-a-paso-desde-cero/), después es necesario conocer unas pocas [tareas de administración del sistema](https://picodotdev.github.io/blog-bitix/2020/02/tareas-basicas-de-administracion-y-uso-despues-de-instalar-una-distribucion-gnu-linux/), [las aplicaciones del entorno de escritorio de GNOME](https://picodotdev.github.io/blog-bitix/2020/02/las-aplicaciones-integradas-del-entorno-de-escritorio-gnome/) y un *listado de programas básicos según categoría en GNU/Linux*. En cada distribución varía ligeramente pero en todas hay que realizar unas tareas básicas de mantenimiento.

Estas tareas básicas de mantenimiento son:

* **Actualizar los paquetes instalados del sistema**. Los paquetes actualizados incluyen correcciones de seguridad por lo que es importante actualizar el sistema de forma regular. También, pueden incluir nuevas versiones de los paquetes con nuevas funcionalidades y correcciones de errores. Una programa que es necesario mantener actualizado es el navegador web, también el núcleo o *kernel* de Linux.
* **Instalar y desinstalar nuevos paquetes y programas**. Dependiendo de las tareas que se deseen realizar hay que instalar los programas que permitan realizarlas. Para editar documentos ofimáticos, un navegador web, retocar imágenes, correo electrónico, descarga de *torrents*, reproductor de vídeo, reproductor de música, captura de imágenes, captura vídeo del escritorio, programas para el desarrollo y programación, virtualización, … Cada programa tienen su paquete en la ditribución que es necesario instalar para usarlo y desinstalar cuando el programa ya no se va a usar más. Es dificil que no encuentres un programa que realice lo que se desea.
* **Uso básico de la terminal**. Hay programas con interfaz gráfica pero para algunas tareas es más rápido hacerlas desde la línea de comandos con la ventaja que con un *script* es posible automatizar en caso de ser repetitiva. Desde la línea de comandos hay numerosos programas útiles que además se pueden combinar de forma que la salida de uno sea la entrada de otro.

Dependiendo de la distribución cada una de estas tareas puede variar el comando en concreto pero en general en todas se realizan de forma similar. A continuación comento como realizar las tareas en dos de las distribuciones más populares como son [Ubuntu](https://www.ubuntu.com/) y [Arch Linux](https://www.archlinux.org/) pero en [Fedora](https://fedoraproject.org/), [Debian](https://www.debian.org/), [elementary OS](https://elementary.io/es/) se realizan de forma similar.
...
index.xmarkdown

» Leer más, comentarios, etc...

Blog Bitix

Cómo migrar el contenido de un sitio web HTML a formato Markdown

febrero 23, 2020 03:00

HTML

Markdown

Wordpress es una opción muy pupular por su facilidad de uso para crear sitios web y blogs. Sin embargo, tiene algunos puntos débiles como necesitar una base de datos para guardar el contenido lo que hace que su instalación, mantenimiento, actualizción o presente problemas de seguridad por necesitar de un procesamiento en este caso PHP para generar las páginas HTML a partir del contenido guardado en la base de datos y sus plugins instalados.

Por estos motivos los generadores de sitios web estáticos al ser mucho más simples son una alternativa, el resultado final es simplemente contenido HTML, hojas de estilo CSS, imágenes, JavaScript y los recursos adicionales que necesite. Todo este contenido estático simplemente necesita de un servidor web no necesita ninguna base de datos lo que lo hace más sencillo instalar en un servidor ni ningún programa que genere el contenido HTML. Además, los generadores de sitios web estáticos permiten editar el contenido en el ordenador local con cuales quiera herramientas instaladas, como Visual Studio Code, entre otras cosas para buscar y reemplazar u aplicar otros comandos de GNU/Linux para transformar el contenidode forma masiva.

Muchos de los generadores estáticos de sitios web como Hugo usan como formato de contenido [Markdown][markdonw]. Markdown utiliza una sintaxis que procesada se convierte con posterioridad a HTML, en los archivos Markdown también puede incluirse trozos de HTML que es emitido sin ningún cambio.

Migrar un sitio web web HTML generador por Wordpress u otro a un generador estático es posible, requiere las siguientes tareas:

  • Recuperar todo el contenido del sitio web original. El contenido complete de un sitio web se puede descargar con un comando de wget.
  • Extraer la parte de contenido propio de la página o del artículo. En Java con la librería jsoup, no es complicado utilizando un selector similar a los empleados por jQuery.
  • Convertir el contenido HTML a Markdown, en Java ofrecen esta funcionalidad las librerías remark y MarkdownJ.
1
$ wget -r -l0 https://picodotdev.github.io/blog-bitix/
wget.sh

Migrar un sitio web HTML con Wordpress sería una tarea que requeriría mucho tiempo, más si tiene muchos artículos, si se hace manualmente copiando y pegando el texto de cada artículo, imágenes, … Creando un script la mayor parte de la tarea se automatiza, seguramente hay que hacer algunas acciones manuales pero con _wget, _jsoup, remark y un script que no tiene por que ser muy largo en líneas de código la tarea es realizable con una inversión de tiempo razonable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html lang="es">
<head>
  <meta name="base" href="/blog-bitix/">
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="author" content="picodotdev">
  <meta name="keywords" content="java, programacion, software, hardware, tapestry, software libre, gnu, linux, gnu/linux, unix, arch, arch linux, html, css, javascript">
  <meta name="description" content="Para utilizar de forma efectiva y eficiente una distribución GNU/Linux es necesario conocer las tareas básicas que hay que realizar en todo sistema. Estas son actualizar los paquetes instalados del sistema a nuevas versiones con correcciones de seguridad, correcciones de errores y mejoras, instalar y desinstalar nuevos paquetes y programas. Conocer el uso básico de la terminal permite automatizar y realizar de forma masiva algunas tareas además de también permitir actualizar el sistema e instalar y desinstalar programas. &hellip;">

  <title>Tareas básicas de administración y uso después de instalar una distribución GNU/Linux</title>
  ...
  <article>
    <h1>Tareas básicas de administración y uso después de instalar una distribución GNU/Linux</h1>
    <p class="information">
        Escrito por <span>picodotdev</span> el <time>09/02/2020</time>.      
        <br>
        <a href="/blog-bitix/tags/gnu-linux/">gnu-linux</a>
        <a href="/blog-bitix/tags/planeta-codigo/">planeta-codigo</a>      
        <br>
        <a href="/blog-bitix/2020/02/tareas-basicas-de-administracion-y-uso-despues-de-instalar-una-distribucion-gnu-linux/">Enlace permanente</a>      
        <a href="/blog-bitix/2020/02/tareas-basicas-de-administracion-y-uso-despues-de-instalar-una-distribucion-gnu-linux/#comments">Comentarios</a>
    </p>
    <p class="summary">Para utilizar de forma efectiva y eficiente una distribución GNU/Linux es necesario conocer las tareas básicas que hay que realizar en todo sistema. Estas son actualizar los paquetes instalados del sistema a nuevas versiones con correcciones de seguridad, correcciones de errores y mejoras, instalar y desinstalar nuevos paquetes y programas. Conocer el uso básico de la terminal permite automatizar y realizar de forma masiva algunas tareas además de también permitir actualizar el sistema e instalar y desinstalar programas.</p>
    ...
    <p>Si has instalado recientemente o piensas instalar una distribución <a href="https://www.gnu.org/">GNU</a>/<a href="https://www.linux.com/">Linux</a> después de <a href="https://picodotdev.github.io/blog-bitix/2016/10/elegir-una-distribucion-gnu-linux-segun-el-usuario-uso-o-equipo/">elegir la distribución GNU/Linux</a> que más se adapte a tus preferecias y de seguir los pasos para <a href="https://picodotdev.github.io/blog-bitix/2017/05/descargar-e-instalar-la-distribucion-ubuntu-de-gnu-linux-paso-a-paso-desde-cero/">instalar una como Ubuntu</a>, después es necesario conocer unas pocas <a href="https://picodotdev.github.io/blog-bitix/2020/02/tareas-basicas-de-administracion-y-uso-despues-de-instalar-una-distribucion-gnu-linux/">tareas de administración del sistema</a>, <a href="https://picodotdev.github.io/blog-bitix/2020/02/las-aplicaciones-integradas-del-entorno-de-escritorio-gnome/">las aplicaciones del entorno de escritorio de GNOME</a> y un <em>listado de programas básicos según categoría en GNU/Linux</em>. En cada distribución varía ligeramente pero en todas hay que realizar unas tareas básicas de mantenimiento.</p>
    <p>Estas tareas básicas de mantenimiento son:</p>
    <ul>
    <li><strong>Actualizar los paquetes instalados del sistema</strong>. Los paquetes actualizados incluyen correcciones de seguridad por lo que es importante actualizar el sistema de forma regular. También, pueden incluir nuevas versiones de los paquetes con nuevas funcionalidades y correcciones de errores. Una programa que es necesario mantener actualizado es el navegador web, también el núcleo o <em>kernel</em> de Linux.</li>
    <li><strong>Instalar y desinstalar nuevos paquetes y programas</strong>. Dependiendo de las tareas que se deseen realizar hay que instalar los programas que permitan realizarlas. Para editar documentos ofimáticos, un navegador web, retocar imágenes, correo electrónico, descarga de <em>torrents</em>, reproductor de vídeo, reproductor de música, captura de imágenes, captura vídeo del escritorio, programas para el desarrollo y programación, virtualización, &hellip; Cada programa tienen su paquete en la ditribución que es necesario instalar para usarlo y desinstalar cuando el programa ya no se va a usar más. Es dificil que no encuentres un programa que realice lo que se desea.</li>
    <li><strong>Uso básico de la terminal</strong>. Hay programas con interfaz gráfica pero para algunas tareas es más rápido hacerlas desde la línea de comandos con la ventaja que con un <em>script</em> es posible automatizar en caso de ser repetitiva. Desde la línea de comandos hay numerosos programas útiles que además se pueden combinar de forma que la salida de uno sea la entrada de otro.</li>
    </ul>
    <p>Dependiendo de la distribución cada una de estas tareas puede variar el comando en concreto pero en general en todas se realizan de forma similar. A continuación comento como realizar las tareas en dos de las distribuciones más populares como son <a href="https://www.ubuntu.com/">Ubuntu</a> y <a href="https://www.archlinux.org/">Arch Linux</a> pero en <a href="https://fedoraproject.org/">Fedora</a>, <a href="https://www.debian.org/">Debian</a>, <a href="https://elementary.io/es/">elementary OS</a> se realizan de forma similar.</p>
    ...
page.html

Descargado el sitio web la tarea del script es por cada artículo del sitio web aplicar un selector de jsoup extraer el contenido y convertirlo a Markdown con remark, también sería tarea del script mover los recursos a la ubicación que requiera el generador de sitios web estáticos del artículo como las imágenes, que el comando wget también descarga.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package io.github.picodotdev.blogbitix.sitemconverter;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import com.overzealous.remark.Remark;
import com.overzealous.remark.Options;

public class Main {

    public static void main(String[] args) throws Exception {
        Stream<Path> files = Files.walk(Paths.get("../picodotdev.github.io/"), 5);
        files.filter(path -> path.getFileName().toFile().getName().endsWith(".html") && path.getFileName()).forEach(path -> {
            try {
                System.out.println(path.toString());

                // Obtener el HTML de una página
                String html = Files.readString(path);

                // Contenido HTML
                System.out.println(html);

                // Parsear el contenido HTML con jsoup
                Document document = Jsoup.parse(html);

                // Obtener los elementos de contenido de la página con selectores
                Elements article = document.select("article");

                // Opciones para convertir a markdown
                Options options = Options.markdown();
                options.preserveRelativeLinks = true;
                options.inlineLinks = true;
                String baseUri = "https://picodotdev.github.io/blog-bitix/";

                // Convertir a markdown
                String markdown = new Remark(options).convertFragment(article.html(), baseUri);

                // Contenido markdown
                System.out.println(markdown);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}
Main.java

Resultado en formato markdown.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Tareas básicas de administración y uso después de instalar una distribución GNU/Linux #

Escrito por picodotdev el 09/02/2020.
[gnu-linux](/blog-bitix/tags/gnu-linux/) [planeta-codigo](/blog-bitix/tags/planeta-codigo/)  
[Enlace permanente](/blog-bitix/2020/02/tareas-basicas-de-administracion-y-uso-despues-de-instalar-una-distribucion-gnu-linux/) [Comentarios](/blog-bitix/2020/02/tareas-basicas-de-administracion-y-uso-despues-de-instalar-una-distribucion-gnu-linux/#comments)

Para utilizar de forma efectiva y eficiente una distribución GNU/Linux es necesario conocer las tareas básicas que hay que realizar en todo sistema. Estas son actualizar los paquetes instalados del sistema a nuevas versiones con correcciones de seguridad, correcciones de errores y mejoras, instalar y desinstalar nuevos paquetes y programas. Conocer el uso básico de la terminal permite automatizar y realizar de forma masiva algunas tareas además de también permitir actualizar el sistema e instalar y desinstalar programas.

![GNU]()

![Linux]()

Si has instalado recientemente o piensas instalar una distribución [GNU](https://www.gnu.org/)/[Linux](https://www.linux.com/) después de [elegir la distribución GNU/Linux](https://picodotdev.github.io/blog-bitix/2016/10/elegir-una-distribucion-gnu-linux-segun-el-usuario-uso-o-equipo/) que más se adapte a tus preferecias y de seguir los pasos para [instalar una como Ubuntu](https://picodotdev.github.io/blog-bitix/2017/05/descargar-e-instalar-la-distribucion-ubuntu-de-gnu-linux-paso-a-paso-desde-cero/), después es necesario conocer unas pocas [tareas de administración del sistema](https://picodotdev.github.io/blog-bitix/2020/02/tareas-basicas-de-administracion-y-uso-despues-de-instalar-una-distribucion-gnu-linux/), [las aplicaciones del entorno de escritorio de GNOME](https://picodotdev.github.io/blog-bitix/2020/02/las-aplicaciones-integradas-del-entorno-de-escritorio-gnome/) y un *listado de programas básicos según categoría en GNU/Linux*. En cada distribución varía ligeramente pero en todas hay que realizar unas tareas básicas de mantenimiento.

Estas tareas básicas de mantenimiento son:

* **Actualizar los paquetes instalados del sistema**. Los paquetes actualizados incluyen correcciones de seguridad por lo que es importante actualizar el sistema de forma regular. También, pueden incluir nuevas versiones de los paquetes con nuevas funcionalidades y correcciones de errores. Una programa que es necesario mantener actualizado es el navegador web, también el núcleo o *kernel* de Linux.
* **Instalar y desinstalar nuevos paquetes y programas**. Dependiendo de las tareas que se deseen realizar hay que instalar los programas que permitan realizarlas. Para editar documentos ofimáticos, un navegador web, retocar imágenes, correo electrónico, descarga de *torrents*, reproductor de vídeo, reproductor de música, captura de imágenes, captura vídeo del escritorio, programas para el desarrollo y programación, virtualización, … Cada programa tienen su paquete en la ditribución que es necesario instalar para usarlo y desinstalar cuando el programa ya no se va a usar más. Es dificil que no encuentres un programa que realice lo que se desea.
* **Uso básico de la terminal**. Hay programas con interfaz gráfica pero para algunas tareas es más rápido hacerlas desde la línea de comandos con la ventaja que con un *script* es posible automatizar en caso de ser repetitiva. Desde la línea de comandos hay numerosos programas útiles que además se pueden combinar de forma que la salida de uno sea la entrada de otro.

Dependiendo de la distribución cada una de estas tareas puede variar el comando en concreto pero en general en todas se realizan de forma similar. A continuación comento como realizar las tareas en dos de las distribuciones más populares como son [Ubuntu](https://www.ubuntu.com/) y [Arch Linux](https://www.archlinux.org/) pero en [Fedora](https://fedoraproject.org/), [Debian](https://www.debian.org/), [elementary OS](https://elementary.io/es/) se realizan de forma similar.
...
index.markdown

» Leer más, comentarios, etc...

Blog Bitix

Las convenciones y guías estilos para el código fuente de Java

febrero 21, 2020 05:00

Java desde su creación ha definido como parte del lenguaje unas convenciones y guías de estilos como recomendación para ser usadas en el código fuente por los programadores que proporcionan homogeneidad en el código fuente y que facilitan su lectura y mantenimiento. El documento no es muy extenso para leerlo y los entornos de desarrollo integrados como IntelliJ permiten formatear el código fuente siguiendo las reglas preestablecidas con una simple combinación de teclas y herramientas como PMD permiten validar de forma automatizada que el código cumple las reglas con la herramienta de construcción o integración continua.

Java

Todos los lenguajes definen un conjunto de reglas que definen el aspecto del código fuente. Estas convenciones cubren la indentación, comentarios, declaraciones, sentencias espacios en blanco, nomenclatura de nombres y prácticas de programación. Son una forma de mejorar la calidad del código que facilita su legibilidad y mantenibilidad.

Es importante seguir en todo el código fuente las mismas convenciones ya que en proyectos de larga duración la parte más importante es la de mantenimiento. El lenguaje de programación Java define sus propias convenciones que generalmente son aceptadas por los programadores. Las convenciones y guías de estilos de Java están recogidas en un documento de recomendable lectura y adhesión al programar. El documento ya tiene unos años pero las reglas existentes desde entonces no han cambiado aún cuando en el lenguaje se han añadido nuevos elementos como las lamdas en Java 8 o la posibilidad de omitir el tipo para las variables locales de Java 11.

Otros lenguajes como Python y C# definen sus propias convenciones bastante diferentes de las de Java que cambia significativamente el aspecto del código.

Convenciones de código en Java

Algunas recomendaciones en Java son:

  • Una declaración de variable por línea, preferiblemente al inicio de los bloques de código.
  • Ajustar la longitud de las líneas a 70 caracteres.
  • Al ajustar líneas poner el punto de ruptura después de la coma, antes del operador, alinear la siguiente línea al inicio de la expresión de la línea anterior.
  • No dejar un espacio en blanco entre el nombre del método y el paréntesis (, la llave de apertura { de inicio del bloque de código en la misma línea precedida por un espacio en blanco y la llave de cierre } indentada a la misma altura que el su bloque.
  • Cada línea debería tener una sola sentencia.
  • Usar líneas en blanco para separar secciones, entre definición de clases e interfaces, entre métodos, entre variables y la primera sentencia.
  • Usar un espacio entre una palabra clave (if, for, while, …) y el paréntesis a continuación. Todos los operadores excepto el punto ., los de incremento ++ y decremento -- deben separarse de sus operandos con un espacio.
  • Reglas de nomenclatura: los nombres de las clases debería ser nombres con la primera letra de cada palabra que lo compone en mayúscula, las interfaces siguen las mismas reglas de capitalización. Los métodos debería ser verbos con la primera letra en minúscula y las primeras letras de cada palabra en mayúscula. Las variables tiene la capitalización de la primera letra en minúscula y las primeras letras de cada palabra en mayúscula con nombre cortos pero significativos. Las variables de una sola letra deben ser evitadas salvo los casos comúnmente reconocidos como iteradores (i, j, k). Las constantes deben estar con todas las letras en mayúscula con las palabras separadas con una barra baja _.

Aparte de las convenciones del propio lenguaje Java otras organizaciones como Google y Spring definen sus propias convenciones cambiando ligeramente las de Java por las preferencias de sus usuarios. Cualquier otra empresa según las preferencias acordadas por sus desarrolladores también puede definir sus variaciones a las convenciones generales de Java, salvo cambiar drásticamente las convenciones generales no hay ningún inconveniente en incorporar pequeñas variaciones lo importante es que todos los desarrolladores sigan las mismas convenciones en todo el código fuente.

Como ejemplo de pequeñas variaciones prefiero declarar las variables en el momento del primer uso en el que se le puede asignar un valor en vez del inicio del bloque de código o con las pantallas de gran resolución en mi opinión el límite de línea máximo puede ser más amplio que 70 caracteres.

Ejemplos de código con convenciones de Java

Este es el aspecto de algunos pequeños trozos de código siguiendo las convenciones definidas por Java.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package java.awt;

import java.awt.peer.CanvasPeer;

public class Main {

   public static void main(String[] args) {
      System.out.println("Hello World!");
   }
}

---

int level = 0;  // indentation level
int size = 0;   // size of table

a += c + d;
a = (a + b) / (c * d);

---

function(longExpression1, longExpression2, longExpression3,
         longExpression4, longExpression5);


longName1 = longName2 * (longName3 + longName4 - longName5)
            + 4 * longname6;


void someMethod(int anArg, Object anotherArg, String yetAnotherArg,
        Object andStillAnother) {
   ...
}

---

if ((condition1 && condition2)
       || (condition3 && condition4)
       || !(condition5 && condition6)) {
   doSomething();
}

if (condition) {
   statements;
} else if (condition) {
   statements;
} else if (condition) {
   statements;
}

for (initialization; condition; update) {
   statements;
}

while (condition) {
   statements;
}

switch (condition) {
    case ABC:
        statements;
        /* falls through */
    case DEF:
        statements;
        break;
    case XYZ:
        statements;
        break;
    default:
        statements;
        break;
}
Main.java

Idioma español o inglés para dar nombres

Otro punto a tener en cuenta en el código fuente es si utilizar palabras del lenguaje materno, en nuestro caso español, o utilizar palabras solo en inglés para dar nombres a clases, métodos y variables. Es válido utilizar cualquiera de las dos opciones siempre que se utilice en todo el código fuente.

Aún así yo prefiero utilizar solo inglés por dos motivos:

  • El inglés es un lenguaje compacto que normalmente utiliza palabras compuestas por menos caracteres que el español.
  • Algunos términos de programación son comúnmente conocidos por sus palabras en inglés como los métodos get y set o patrones de diseño como Repository o Aggregator, mezclar otro lenguaje con las palabras en inglés queda raro (getPrecio(), findProductoByNombreAndActivo(), CompraRepository).
1
2
3
CompraRepository compraRepository;
BigDecimal precio = producto.getPrecio();
Host nominasHost = host;
EspanolMain.java

Por suerte en Java refactorizar cualquier nombre es bastante más sencillo y rápido con el soporte de los IDE. En un lenguaje dinámico hacer un renombrado es básicamente buscar y reemplazar todas las ocurrencias con riesgo de omitir alguna que cause un «error de compilación» en tiempo de ejecución del código erróneo.

Herramientas automatizadas

Los entornos integrados de desarrollo ofrecen la funcionalidad de formatear el código de forma automática con las reglas que tengan configuradas. En IntelliJ se configuran en File > Settings > Editor > Code Style para que todos los desarrolladores utilicen las mismas reglas, estas se pueden compartir con las opciones de exportar e importar en otro ordenador. En estos paneles hay multitud de opciones para personalizar el formateo del código.

Formateo de código y reglas de estilo en IntelliJ IDEA para Java Formateo de código y reglas de estilo en IntelliJ IDEA para Java Formateo de código y reglas de estilo en IntelliJ IDEA para Java

Formateo de código y reglas de estilo en IntelliJ IDEA para Java Formateo de código y reglas de estilo en IntelliJ IDEA para Java Formateo de código y reglas de estilo en IntelliJ IDEA para Java

Formateo de código y reglas de estilo en IntelliJ IDEA para Java

Formateo de código y reglas de estilo en IntelliJ IDEA para Java

Existen herramientas que automatizan la comprobación de las normas elegidas en un proyecto en el código desde la línea de comandos con la herramienta de construcción como Gradle y aplicable también al código subido al repositorio de control de versiones con la herramienta de integración continua ya sea Jenkins, GitLab u otra. Una de ellas es PMD, otra Checkstyle, ambas generan un informe con los errores de convenciones con el que es muy fácil realizar los cambios para corregirlos.

Referencia:

» Leer más, comentarios, etc...

Blog Bitix

Las convenciones y guía de estilos para el código fuente de Java

febrero 21, 2020 05:00

Java desde su creación ha definido como parte del lenguaje unas convenciones y guías de estilos como recomendación para ser usadas en el código fuente por los programadores que proporcionan homogeneidad en el código fuente y que facilitan su lectura y mantenimiento. El documento no es muy extenso para leerlo y los entornos de desarrollo integrados como IntelliJ permiten formatear el código fuente siguiendo las reglas preestablecidas con una simple combinación de teclas y herramientas como PMD permiten validar de forma automatizada que el código cumple las reglas con la herramienta de construcción o integración continua.

Java

Todos los lenguajes definen un conjunto de reglas que definen el aspecto del código fuente. Estas convenciones cubren la indentación, comentarios, declaraciones, sentencias espacios en blanco, nomenclatura de nombres y prácticas de programación. Son una forma de mejorar la calidad del código que facilita su legibilidad y mantenibilidad.

Es importante seguir en todo el código fuente las mismas convenciones ya que en proyectos de larga duración la parte más importante es la de mantenimiento. El lenguaje de programación Java define sus propias convenciones que generalmente son aceptadas por los programadores. Las convenciones y guías de estilos de Java están recogidas en un documento de recomendable lectura y adhesión al programar. El documento ya tiene unos años pero las reglas existentes desde entonces no han cambiado aún cuando en el lenguaje se han añadido nuevos elementos como las lamdas en Java 8 o la posibilidad de omitir el tipo para las variables locales de Java 11.

Otros lenguajes como Python y C# definen sus propias convenciones bastante diferentes de las de Java que cambia significativamente el aspecto del código.

Convenciones de código en Java

Algunas recomendaciones en Java son:

  • Una declaración de variable por línea, preferiblemente al inicio de los bloques de código.
  • Ajustar la longitud de las líneas a 70 caracteres.
  • Al ajustar líneas poner el punto de ruptura después de la coma, antes del operador, alinear la siguiente línea al inicio de la expresión de la línea anterior.
  • No dejar un espacio en blanco entre el nombre del método y el paréntesis (, la llave de apertura { de inicio del bloque de código en la misma línea precedida por un espacio en blanco y la llave de cierre } indentada a la misma altura que el su bloque.
  • Cada línea debería tener una sola sentencia.
  • Usar líneas en blanco para separar secciones, entre definición de clases e interfaces, entre métodos, entre variables y la primera sentencia.
  • Usar un espacio entre una palabra clave (if, for, while, …) y el paréntesis a continuación. Todos los operadores excepto el punto ., los de incremento ++ y decremento -- deben separarse de sus operandos con un espacio.
  • Reglas de nomenclatura: los nombres de las clases debería ser nombres con la primera letra de cada palabra que lo compone en mayúscula, las interfaces siguen las mismas reglas de capitalización. Los métodos debería ser verbos con la primera letra en minúscula y las primeras letras de cada palabra en mayúscula. Las variables tiene la capitalización de la primera letra en minúscula y las primeras letras de cada palabra en mayúscula con nombre cortos pero significativos. Las variables de una sola letra deben ser evitadas salvo los casos comúnmente reconocidos como iteradores (i, j, k). Las constantes deben estar con todas las letras en mayúscula con las palabras separadas con una barra baja _.

Aparte de las convenciones del propio lenguaje Java otras organizaciones como Google y Spring definen sus propias convenciones cambiando ligeramente las de Java por las preferencias de sus usuarios. Cualquier otra empresa según las preferencias acordadas por sus desarrolladores también puede definir sus variaciones a las convenciones generales de Java, salvo cambiar drásticamente las convenciones generales no hay ningún inconveniente en incorporar pequeñas variaciones lo importante es que todos los desarrolladores sigan las mismas convenciones en todo el código fuente.

Como ejemplo de pequeñas variaciones prefiero declarar las variables en el momento del primer uso en el que se le puede asignar un valor en vez del inicio del bloque de código o con las pantallas de gran resolución en mi opinión el límite de línea máximo puede ser más amplio que 70 caracteres.

Ejemplos de código con convenciones de Java

Este es el aspecto de algunos pequeños trozos de código siguiendo las convenciones definidas por Java.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package java.awt;

import java.awt.peer.CanvasPeer;

public class Main {

   public static void main(String[] args) {
      System.out.println("Hello World!");
   }
}

---

int level = 0;  // indentation level
int size = 0;   // size of table

a += c + d;
a = (a + b) / (c * d);

---

function(longExpression1, longExpression2, longExpression3,
         longExpression4, longExpression5);


longName1 = longName2 * (longName3 + longName4 - longName5)
            + 4 * longname6;


void someMethod(int anArg, Object anotherArg, String yetAnotherArg,
        Object andStillAnother) {
   ...
}

---

if ((condition1 && condition2)
       || (condition3 && condition4)
       || !(condition5 && condition6)) {
   doSomething();
}

if (condition) {
   statements;
} else if (condition) {
   statements;
} else if (condition) {
   statements;
}

for (initialization; condition; update) {
   statements;
}

while (condition) {
   statements;
}

switch (condition) {
    case ABC:
        statements;
        /* falls through */
    case DEF:
        statements;
        break;
    case XYZ:
        statements;
        break;
    default:
        statements;
        break;
}
Main.java

Idioma español o inglés para dar nombres

Otro punto a tener en cuenta en el código fuente es si utilizar palabras del lenguaje materno, en nuestro caso español, o utilizar palabras solo en inglés para dar nombres a clases, métodos y variables. Es válido utilizar cualquiera de las dos opciones siempre que se utilice en todo el código fuente.

Aún así yo prefiero utilizar solo inglés por dos motivos:

  • El inglés es un lenguaje compacto que normalmente utiliza palabras compuestas por menos caracteres que el español.
  • Algunos términos de programación son comúnmente conocidos por sus palabras en inglés como los métodos get y set o patrones de diseño como Repository o Aggregator, mezclar otro lenguaje con las palabras en inglés queda raro (getPrecio(), findProductoByNombreAndActivo(), CompraRepository).
1
2
3
CompraRepository compraRepository;
BigDecimal precio = producto.getPrecio();
Host nominasHost = host;
EspanolMain.java

Por suerte en Java refactorizar cualquier nombre es bastante más sencillo y rápido con el soporte de los IDE. En un lenguaje dinámico hacer un renombrado es básicamente buscar y reemplazar todas las ocurrencias con riesgo de omitir alguna que cause un «error de compilación» en tiempo de ejecución del código erróneo.

Herramientas automatizadas

Los entornos integrados de desarrollo ofrecen la funcionalidad de formatear el código de forma automática con las reglas que tengan configuradas. En IntelliJ se configuran en File > Settings > Editor > Code Style para que todos los desarrolladores utilicen las mismas reglas, estas se pueden compartir con las opciones de exportar e importar en otro ordenador. En estos paneles hay multitud de opciones para personalizar el formateo del código.

Formateo de código y reglas de estilo en IntelliJ IDEA para Java Formateo de código y reglas de estilo en IntelliJ IDEA para Java Formateo de código y reglas de estilo en IntelliJ IDEA para Java

Formateo de código y reglas de estilo en IntelliJ IDEA para Java Formateo de código y reglas de estilo en IntelliJ IDEA para Java Formateo de código y reglas de estilo en IntelliJ IDEA para Java

Formateo de código y reglas de estilo en IntelliJ IDEA para Java

Formateo de código y reglas de estilo en IntelliJ IDEA para Java

Existen herramientas que automatizan la comprobación de las normas elegidas en un proyecto en el código desde la línea de comandos con la herramienta de construcción como Gradle y aplicable también al código subido al repositorio de control de versiones con la herramienta de integración continua ya sea Jenkins, GitLab u otra. Una de ellas es PMD, otra Checkstyle, ambas generan un informe con los errores de convenciones con el que es muy fácil realizar los cambios para corregirlos.

Referencia:

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 391

febrero 19, 2020 05:39

Enlaces interesantes Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin / Mobile

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Poesía Binaria

Limitar el uso de CPU de nuestras aplicaciones o procesos en GNU/Linux (señales, nice, cpulimit/cputool, cgroups, systemd slices)

febrero 19, 2020 09:10

Algo que nos puede traer de cabeza como administradores de sistemas y, a veces como usuarios es el hecho de que un proceso se coma, devore y trate sin piedad la CPU de nuestro sistema. Ya no solo el hecho de que un programa deje inservible el ordenador, aunque a día de hoy con tantos núcleos de procesador, casi siempre tendremos algo de CPU para enviar una señal de parada a un proceso. Un ejemplo más claro puede que tengamos dos programas en ejecución (o más), uno de ellos nos corre más prisa que el otro, ¡pero el ordenador no lo sabe! Así que, si utilizan la CPU de forma intensiva terminarán utilizando el 50% de la CPU cada uno.

Pero claro, un proceso necesitamos que esté listo antes, lo lógico es asignarle más CPU a éste. ¿Qué opciones tenemos?

La medida obvia, ejecución secuencial

Lo primero que se nos pasa por la cabeza es ejecutar primero el proceso que nos corre prisa, y cuando termine ejecutar el otro. Esto puede ser una buena solución en muchas ocasiones. Pero claro, ¿y si los procesos llevan lanzados ya un tiempo y han avanzado en su progreso? detener uno de ellos puede suponer la pérdida del estado que se ha conseguido. Puede que nos compense, o puede que no.
¿Y si estamos hablando de programas demonio? Los programas están en ejecución en segundo plano, haya momentos de gran utilización de CPU y momentos en los que no se usa, pero sí sabemos que uno tiene preferencia sobre otro. O al menos no queremos dejar sin CPU a los demás programas en ejecución.

Señales (peleando con la terminal)

Lo siguiente que se nos puede ocurrir es pausar y reanudar procesos. Si tenemos los procesos en dos terminales por separado, podemos pulsar Control+Z en el que deseemos pausar y luego escribir fg para reanudarlo. No tiene pérdida. Pero si los procesos no tienen un terminal, también podemos hacerlo enviándoles las señales SIGSTOP y SIGCONT. Para poner un ejemplo, he creado un pequeño script en Bash (muy tonto y lo he llamado consumidor.sh):

1
2
3
#!/bin/bash

while (( 1 )); do echo CONSUMO CPU; done

Ahora lo ejecuto y me olvido de esa terminal porque no puedo tocarla. Desde otra terminal ejecuto top.
En lugar de consumidor.sh podríamos utilizar la siguiente línea:

echo «scale=100000; a(1)*4» | bc -l

Que se pondrá a calcular con un solo núcleo 100000 dígitos del número Pi.

top del consumidor
Como vemos, tenemos un proceso consumidor.sh que está tirando bastante de CPU, así que, conociendo el PID (process ID o identificador de proceso) podemos ejecutar:

kill -SIGSTOP 11023

En ese momento vemos que ya no es un proceso que consuma CPU. Sigue en ejecución, pero está en pausa. Que no os asuste el comando kill, que no estamos matando a nadie, aún, sólo le decimos al sistema operativo que no le de más CPU hasta nueva orden, dicha orden es:
kill -SIGCONT 11023

Y tras ejecutar esto volveremos a ver el proceso en cuestión dar guerra.
Si no queremos utilizar el PID del proceso, podemos hacerlo por su nombre utilizando (con cuidado, si tenemos varios procesos con el mismo nombre pararemos todos):
killall -SIGSTOP consumidor.sh

O el comando pkill de la misma manera.

De todas formas, es un engorro para nosotros estar todo el rato enviando señales es demasiado trabajo. Como humanos, si tenemos muchos procesos nos podemos confundir de PID y por otro lado tenemos que estar pendientes de cuando un proceso empieza a consumir CPU para pararlo. Si lo pensamos es una tarea muy automatizable.

Nice

La llamada del sistema que nos permite establecer la prioridad de un proceso con respecto al programador de tareas es nice. Pero solo es un pequeño factor que influye en la asignación de CPU a cada proceso por parte del sistema operativo. Imaginémonos una cola de supermercado, darle más nice a un proceso sería colocarlo un poco antes o después en la cola. El problema es que esta cola se repite muchas veces porque hay muchos procesos en ejecución (si tuviéramos que esperar que se terminara un proceso para ejecutar otro, nos podríamos olvidar de la multitarea y de muchas otras cosas). Si cada vez que un proceso se coloca en la cola, no lo ponemos el último, sino que lo colocamos más cerca de la caja, al final le estaremos asignando más tiempo de CPU al proceso.
Para ver resultados al cambiar el nice del proceso. Debemos tener una CPU pequeña (de un VPS pequeño, por ejemplo), o tener muchísimas tareas en ejecución, porque si tenemos CPU de sobra, el sistema siempre va a encontrar tiempo de CPU para un proceso. Si tenemos 8 core y estamos utilizando solo 2 al 100%, ahora queremos ejecutar otra aplicación (monohilo) con baja prioridad, el sistema verá que tenemos 6 core muertos de risa, así que la aplicación se ejecutará consumiendo el 100% de uno de los core sobrantes, porque ninguna otra aplicación reclama CPU, y el sistema ve que hay recursos de sobre. Por eso notaremos estos cambios cuando tengamos menos CPU para asignar.

Podemos ejecutar un programa con un nice determinado así:

nice -n xx consumidor.sh

Donde xx es un número entre -20 (más favorable) y 19 (menos favorable). Es decir, con -20 colaremos todo lo que podamos al proceso y con 19 lo pondremos lo más lejos de la caja que podamos. Con el 19, el proceso solo obtendrá tiempo de CPU cuando ningún otro proceso reclame CPU. Nota: También podemos usar nice -10 o nice 5, quitando el -n del medio, para teclear menos.

Pero también es posible cambiar el nice, o prioridad del proceso cuando éste está en ejecución, utilizando su PID (también podemos asignar prioridad a todos los procesos de un grupo o usuario con -u y -g):

renice -n -20 -p 11023

En este caso también podremos quitar el -n si queremos.

ionice

Cuando un proceso utiliza además de CPU, entrada/salida de datos. Por ejemplo lectura y escritura de disco. Podemos acelerar o decelerar dicha operación. Normalmente un proceso cuando realiza una operación de disco, la solicita al sistema operativo. El proceso pasa a un estado de espera, porque no puede continuar sin el dato o el resultado de la operación, y cuando se hace efectiva, el sistema operativo vuelve a meter al proceso en la cola del programador de tareas para que pueda tener CPU de nuevo.
Ahora bien, si las operaciones de entrada/salida tardan poco, el proceso que las origina tardará menos en terminar. Las operaciones de I/O también van en cola y podemos hacer que éstas se realicen con prioridad máxima (tal cual entran, se realizan), con prioridad mínima (solo se llevarán a cabo cuando no haya ninguna otra operación de I/O por hacer), o en varios términos medios.
Si, por ejemplo, nuestro proceso solo usa CPU y poco I/O, cambiar esto apenas influirá.

Para cambiar el nice de IO (ionice), tenemos la orden ionice que podemos usar así:

ionice -c3 -p 10271

Para establecer prioridad mínima, el argumento -c (clase) a 3 (idle, cuando el sistema está desocupado). O para máxima prioridad,

ionice -c1 -n0 -p 10271

Siendo -c1 (clase 1, realtime o tiempo real) y -n0 (máxima prioridad dentro de la clase).

Como caso general -n obtendrá un valor entre 0 y 7 donde 0 es máxima prioridad y 7 es la mínima. Faltaría la clase 2 (best effort), que sería algo intermedio y también tendrá prioridades entre 0 y 7.

cpulimit/cputool

Ambos programas utilizan la misma técnica, y lo que hacen es automatizar el envío de señales SIGSTOP y SIGCONT a los procesos. Es decir, parar y reanudar procesos continuamente. De este modo es como si constantemente un programa estuviera mirando el porcentaje de CPU de top y si ve que un proceso se pasa de lo que hemos establecido, lo pausa, cuando ve que el porcentaje ha bajado, lo reanuda y así continuamente.
Como concepto está bien, pero claro, el propio cpulimit (cputool también) consume ya CPU, haciendo llamadas a sistema para ver el % de CPU de los procesos y luego hace más llamadas a sistema para pausar y reanudar procesos, por lo tanto estamos consumiendo CPU para ello. Es verdad que la cantidad de CPU que consumimos comparada con un proceso que devora CPU es pequeña, pero si en realidad el proceso devora CPU, la aplicación se pasa el rato pausando y reanudando.

Para probar esto, primero instalamos la utilidad cpulimit (seguro que nuestra distribución la tiene en su repositorio) y hacer lo siguiente:

cpulimit -l 10 -p 11023

Con esto le decimos al proceso con PID 11023 (el que lanzamos antes) que no consuma más de un 10% de CPU. Esto no tendrá precisión científica, como vemos en la captura de pantalla (está consumiendo 15,2%) depende de muchos factores. El primero de ellos es el algoritmo con el que se calcula el porcentaje de CPU, que puede hacer que varíe un poco. También influye que el proceso cpulimit esté corriendo con un nice alto (al estar antes en la cola de la CPU hace más frecuentemente los cálculos de porcentaje de CPU y puede actuar antes) y que el proceso utilice gran cantidad de CPU, haga o no llamadas a sistema y demás acciones que pueden hacer que el programador de tareas se detenga más o menos en el proceso en cuestión.


Para observar el comportamiento de cpulimit, debemos observar la columna S del comando top (en la captura), veremos cómo el estado del proceso va cambiando de T (stopped, parado, cuando envían SIGSTOP) a R (running, en ejecución, cuando reanudan el proceso con SIGCONT).

Cpulimit nos permite hacer algunas cosas más, como por ejemplo, con -m podemos monitorizar también todos los procesos hijos, porque muchos procesos se dividen en varios subprocesos nada más ejecutarse para trabajar.

Cputool funciona de forma parecida, en este caso, debemos utilizar -c:

cputool -c 10 -p 11023

Pero la técnica utilizada es la misma. Con el argumento -vv podremos ver cuándo cputool dice de pausar y cuándo de reanudar, es hipnótico y muy educativo, pero al final lo que hacemos es pausar y reanudar el proceso constantemente. Algo que pasa desde una aplicación, al kernel, o núcleo del sistema operativo, muchas veces.

Tanto con cputool como con cpulimit podemos ejecutar la aplicación directamente desde el comando y no especificar PID. En este caso serviría para aplicaciones que estamos ejecutando ahora y no para las que se encuentran ya en ejecución

Control Groups (cgroups)

Los grupos de control o control groups nos permiten limitar los recursos de un grupo de procesos en Linux. Estos grupos nos permiten controlar el tiempo de CPU, memoria, ancho de banda de red o de entrada/salida, etc. disponibles para ese grupo de procesos. Y en este caso es el kernel el que controla (como el nice), así que todo queda en casa, sin un proceso extra que regule y calcule y le diga al kernel lo que tiene que hacer (SIGSTOP, SIGCONT, etc). Configurar los control groups puede ser un poco más trabajoso que utilizar cpulimit, pero a la larga nos dará más alegrías. Además hablamos de grupos de procesos y no de procesos sueltos, por lo que podemos meter varios procesos en un grupo y tendrán las mismas políticas.

Los cgroups se usan muchísimo por docker, systemd, Hadoop, Kubernetes, LxC y muchos más proyectos. Son una herramienta potentísima para llevar el control de procesos.

Aunque sea el kernel el que controla y decide, necesitamos aplicaciones de espacio de usuario que definan los parámetros de control. Vamos, programas que hablen con el kernel para decir qué queremos; igual que kill, nice y demás utilidades.

Para ello podemos instalar las cgroup-toolso libcgroup-tools, dependiendo de la distribución puede tener otro nombre parecido. Una vez instaladas, nos disponemos a crear nuestro cgroup:

sudo cgcreate -g cpu:/cpulimitada

Con esto creamos un grupo dedicado a limitar CPU llamado cpulimitada. Podemos crear otro tipo de grupos que controlen memory: (memoria), cpuset: (que una aplicación use determinados núcleos), blkio: (entrada/salida), net-prio: (prioridad de red) y algunos más. Aunque este post se centrará en CPU.

Tras esto, debemos añadir nuestro proceso al cgroup (podríamos configurar el cgroup primero, pero como tenemos un terminal abierto con top, me gusta observar lo que pasa en todo momento. Para añadir nuestro proceso tenemos dos opciones, lanzar el proceso así:

sudo cgexec -g cpu:/cpulimitada consumidor.sh

Aunque, si por el contrario, el proceso ya está lanzado con anterioridad y queremos modificar su comportamiento podremos hacer lo siguiente:
sudo cgclassify -g cpu:/cpulimitada 11023 consumidor.sh

Aunque se llamen grupos, un grupo de procesos puede contener un solo proceso, para hacer un ejemplo nos viene bien, luego podemos meter más procesos si queremos.

Una vez tenemos el proceso dentro de nuestro grupo y top no ha mostrado nada diferente ni raro. Por defecto, el grupo no tiene restricciones de CPU, podemos hacer lo siguiente:

sudo cgset -r cpu.cfs_quota_us=20000 cpulimitada

La forma de dar el dato de cantidad de CPU a asignar es un poco diferente. Aquí establecemos los microsegundos de CPU que se dedicarán al proceso por intervalo de tiempo. En este caso dedicaremos 20000µs (20ms) por cada 0.1segundos (por cada 100000µs o 100ms), resultará un 20% del tiempo total. (20% de CPU)
Con cgroups estamos eliminando factores de la ecuación, por lo que no dependerá de un proceso externo, ni de la prioridad del proceso, además, no estaremos enviando señales y cambiando el estado del proceso constantemente. Será el propio kernel el que asigne CPU a un proceso o no. Por lo que todo será más limpio eficiente.

Con los cgroups podremos además definir de cuánto será el intervalo de tiempo (antes dijimos que era de 100000µs, que es el valor por defecto) de la siguiente forma y dejarlo en un segundo (1000000µs):

sudo cgset -r cpu.cfs_period_us=1000000 cpulimitada

Tenemos mucho más control sobre los procesos con cgroups. Para obtener más información podemos echar un vistazo a la documentación de RedHat. Incluso podríamos controlar los cgroups desde el sistema de archivos (en muchas distribuciones tenemos los cgroups montados en /sys/fs/cgroup/, o podemos montar el directorio virtual donde queramos, igual que /proc).

Este post pretende ser solo una pincelada, así que para curiosear con las cgroup-tools podremos investigar los comandos: lscgroup, cgget, cgset, cgcreate, cgdelete. Hay más, pero creo que estos son los más importantes.

systemd-slices


Como dijimos antes, systemd utiliza muchísimo los cgroups, generalmente tiene un cgroup para cada servicio con todos los procesos que despliegue cada uno de ellos. Podemos personalizar todo esto. Ahora, creemos un servicio de systemd, que contenga lo siguiente (yo lo he llamado /etc/systemd/system/poesiatest.service):

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=Gascripts
After=network.target

[Service]
Type=exec
ExecStart=/home/gaspy/consumidor.sh
CPUAccounting=true
CPUQuota=20%

[Install]
WantedBy=multi-user.target

Como hemos puesto al final de la etiqueta Service, utilizamos la clave CPUAccounting para indicarle a systemd que queremos limitar la CPU de ese servicio, y CPUQuota para especificar el porcentaje de CPU con el que lo vamos a limitar. Arrancamos el servicio:

sudo systemctl daemon-reload
sudo systemctl start poesiatest.service

Y cuando miremos top veremos que la CPU que consume dicho proceso está limitada al 20% (más o menos, siempre hay un poco de margen).

La gracia está en que podemos hacer que un servicio en ejecución varíe su cuota de CPU:

sudo systemctl set-property poesiatest.service CPUQuota=50%

Pero esto no está limitado a servicios de systemd, podemos ejecutar un programa tal y como lo hacíamos con cpulimit, pero utilizando un slice que limite las características de la ejecución del programa. Por detrás utilizará cgroups, pero el resultado es mucho más amigable para el usuario (o sea, nosotros). Primero creamos la slice (luego podemos modificar sus características con systemctl set-property por lo que no es algo fijo). Nuestra slice será un archivo en /etc/systemd/system/mislice.slice que contiene:

1
2
3
4
5
[Unit]
Description=Mi slice

[Slice]
CPUQuota=40%

Ahora para ejecutar un programa con esa slice hacemos:

systemd-run --user --slice=mislice.slice consumidor.sh

Utilizaremos –user para ejecutar el programa como usuario.

Si por el contrario el proceso ya está iniciado, podremos utilizar cgclassify (como antes), ya que nuestro slice al final es un cgroup:

sudo cgclassify -g cpu:mislice.slice 11023

Y, por supuesto, sin ningún problema, podremos variar la cuota de CPU para dicho grupo:

sudo systemctl set-property mislice.slice CPUQuota=90%

CPU shares

Una forma justa de asignar CPU a grupos de procesos o cgroups es a través de shares. Son como participaciones en la rifa de tiempo de CPU. Como no estamos definiendo el porcentaje de CPU que le corresponde a cada grupo o a cada proceso es algo más difícil de ver. En este caso le estamos diciendo al sistema operativo la cantidad de CPU relativa que le corresponde a cada grupo.
Por ejemplo, tenemos 100 participaciones o shares y tenemos dos procesos. Un proceso tiene 50 y el otro tiene otras 50. Este es el caso por defecto, en el que el mismo tiempo de CPU le corresponde a cada proceso. Ahora bien, si un proceso tiene 75 y el otro 25. El proceso que más participaciones tiene tendrá más tiempo de CPU asignado que el proceso que tiene 25. Hasta aquí todo es más o menos lógico. La gracia de las participaciones está en la CPU que necesitan los procesos. Si por ejemplo el proceso que tiene 75 shares está esperando que el usuario pulse una tecla, y el proceso de 25 shares está calculando dígitos del número Pi (una tarea que utiliza intensamente la CPU), nuestro sistema operativo, mientras el usuario pulsa o no la tecla que requiere el primer proceso asignará el 100% de CPU (o toda la que sea posible) al proceso que solo tiene 25 participaciones. Es decir, las participaciones tienen efecto cuando hay una disputa por la CPU. En caso que haya CPU para todos los procesos, el sistema operativo entregará los recursos sin mirar mucho.
Por ejemplo, si tenemos un servidor web, y en la misma máquina un gestor de tareas en segundo plano para extraer información, procesar ficheros pesados de usuarios, etc. Nos interesa más que haya CPU para procesar las peticiones de los usuarios con la menor demora posible, por lo que si entra una nueva tarea de un usuario, no queremos que se lleve todos los recursos del procesador. Podemos hacer que el servidor web tenga 90 participaciones y el gestor de tareas tenga 10. Eso sí, si estamos en una hora en la que no tenemos usuarios en la web o tenemos muy pocos, el gestor de tareas podrá consumir toda la CPU si lo necesita.

Para utilizar las CPU shares, podemos hacerlo tanto con cgset como con systemctl set-property, de la siguiente manera:

sudo cgset -r cpu.shares=512 cpulimitada
sudo systemctl set-property mislice.slice CPUShares=512

Teniendo en cuenta que el número de shares que tiene un grupo por defecto es de 1024. En el ejemplo, estamos reduciendo la cantidad de participaciones, por lo que le bajamos prioridad a la asignación de CPU. Si queremos podemos subirla a 2048, 4096, etc. Personalmente, suelo utilizar múltiplos y submúltiplos de 1024 para hacer rápidamente el cálculo de probabilidad de cabeza. Pero podemos utilizar otras cantidades como 100, 1000, 1234, etc.

Es una buena técnica, sobre todo si estamos experimentando, anotar el valor de shares, que tiene un grupo (si no es de 1024), para eso podemos hacer:

sudo cgget -r cpu.shares cpulimitada
cpulimitada:
cpu.shares: 512

Un detalle más sobre systemd y cgroups

Como hemos visto systemd también administra cgroups en nuestro sistema y, por defecto, crea muchos grupos y nos deja una configuración base hecha. Es cierto que solo están los procesos agrupados, pero no tenemos ninguna restricción, eso nos lo deja a nosotros. Pero si somos usuarios de una distribución que utilice systemd podemos sacar provecho de todo esto.
Solo tenemos que echar un vistazo a lo siguiente:

systemd-cgls

Nos devuelve algo como esto:

Es un listado de todos los slices de manera jerárquica. Si observamos con atención podemos ver que hay un slice para usuarios (user.slice), para el sistema (system.slice) y a veces no aparece si no está en uso, otro para máquinas virtuales y linux containers (machine.slice). Eso nos permite establecer propiedades con systemctl set-property sobre esos slices. Por ejemplo:
sudo systemctl set-property user.slice CPUQuota=30%

Para que los usuarios no puedan exceder una cuota determinada de CPU. Aunque también vemos que cada usuario tiene su slice. Por ejemplo, user-1000.slice, por lo que podríamos decir:
sudo systemctl set-property user.slice CPUQuota=40%

Para que dicho usuario no pueda exceder esa cuota de CPU. Algo que puede ser de gran utilidad si administramos un servidor con varios usuarios y no queremos que alguno de ellos se pase.

Por ejemplo, también podríamos aplicar restricciones a Apache, PHP, MySQL, postfix, o demás servicios del modo habitual. O incluso a diferentes contenedores de docker (vía containerd.service). Además, si queremos ver qué cgroups están consumiendo CPU, memoria y demás, tenemos una utilidad systemd-cgtop, que nos muestra la información como el comando top, sencilla y actualizada cada segundo.

Más información

Si quieres más información sobre prioridad de procesos y cgroups:

Hay muchas más webs que hablan de ello, pero estas son algunas que he consultado para escribir este post.

Foto principal: unsplash-logoOleg Gospodarec

Foto pizza: unsplash-logoHeather Gill

Binarideas logo

¿Necesitas un sysadmin?

Si te ha gustado el post y encuentras interesante lo que cuento en materia de sistemas. O si necesitas gestionar un servidor (o muchos), automatizar procesos o mejorar la calidad de los procesos actuales. No dudes en ponerte en contacto conmigo.

The post Limitar el uso de CPU de nuestras aplicaciones o procesos en GNU/Linux (señales, nice, cpulimit/cputool, cgroups, systemd slices) appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Poesía Binaria

Reanudando la marcha después de un año

febrero 18, 2020 09:14

¡Hace poco más de un año que no publico nada! Seguro que muchos de los que leíais el blog a menudo pensabais que estaba abandonado. Lo cierto es que en este último año han ocurrido varias cosas tanto en mi vida profesional como personal que me han impedido seguir con el ritmo de publicación.
Hace muchísimo tiempo que no hablo de algún factor personal en este blog. Llevo más de 10 años con él y 10 años dan para mucho, ¡No he celebrado el aniversario del blog! ¡No tengo perdón!

Lo principal, el año pasado nació mi primer hijo, muy querido y deseado. Y claro, la cantidad de ratos libres durante el día desciende de manera implacable. Es más, cuando por fin tienes un rato libre, para ti solo, todo está hecho (cosa rara), y el pequeño está dormido, prefieres descansar un poco. Aún así, en este tiempo, he empezado cerca de diez posts que poco a poco iré revisando y sacando de los borradores.

Por otro lado, a veces he escuchado a personas decir que tener un hijo les cambia la perspectiva de manera radical. Es cierto que te planteas la vida de manera distinta. Tanto que, empujado también por otros factores, dejé mi trabajo. Y desde entonces intento salir adelante como autónomo/emprendedor/mente inquieta, ofreciendo mis servicios y experiencia como desarrollador y administrador de sistemas bajo el nombre de Binarideas.
Siempre he tenido muchas ideas, y me han gustado la programación y los retos y había muchas cosas que aprender tanto de contabilidad, organización, presupuestos, trato con clientes, fiscalidad (aunque una asesoría me lleva algunos temas, algo tiene que sonarme todo, digo yo). Además, tenía que desarrollar una serie de herramientas que me iban a ayudar a sacar adelante mis proyectos y eso me quitaba mucho más tiempo para el blog. En cualquier caso, también ofrezco servicios de desarrollo a medida y administración de sistemas. Si crees que puedo ayudarte, contacta conmigo, si vienes del blog, seguro que te hago una oferta.

Como me dijo un amigo (no recuerdo las palabras exactas), hay que estar loco para tener un hijo y dejar el trabajo. Y no le falta razón.

En cualquier caso, este blog quedará reservado para temas técnicos, aunque me haré algo de publicidad y, por supuesto, como persona comprometida con el software libre, muchas de las herramientas, scripts y técnicas que he desarrollado, las compartiré por aquí, comentadas y explicadas así como en plataformas como Github o Gitlab.

También tengo que decir que la frecuencia de publicación de posts bajará un poco. Antes, intentaba tener un artículo cada semana, incluso tenía varios programados para lanzarse los lunes que coincidía con el día de más visitas (depende de la temporada cae en lunes o miércoles), pero ahora seguramente la frecuencia bajará un poco, ¡pero este blog no está abandonado!

Eso sí, ¡mañana, toca post! Largo y muy interesante. También tengo muchos e-mails y comentarios que debo contestar. ¡Todos serán contestados!

Foto principal: unsplash-logoKai Pilger

The post Reanudando la marcha después de un año appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Variable not found

Rutado dinámico en ASP.NET Core 3 MVC

febrero 18, 2020 07:05

ASP.NET Core MVCEn muchas ocasiones, la llegada de una nueva versión de un framework viene acompañada de documentación, posts y otro tipo de contenidos que anuncian a bombo y platillo las principales novedades y cambios introducidos. Ante este ruido, es fácil pasar por alto otros pequeños cambios que introducen mejoras a los marcos de trabajo o simplemente permiten ir evolucionando código antiguo a funcionalidades más recientes.

Hoy vamos a hablar de una de eso pequeños descubrimientos: el rutado dinámico de controladores. Introducido en ASP.NET Core 3, se trata de una característica que no ha sido especialmente publicitada ni comentada, pero puede ser de utilidad en algunas ocasiones, pues permite interferir en la forma de interpretar las rutas de las peticiones entrantes con objeto de decidir en cada caso cómo deben ser procesadas.

Rutado estático vs rutado dinámico

Seguro que muchos estáis ya empezando a utilizar el flamante endpoint routing para definir las rutas hacia controladores o páginas Razor de vuestras aplicaciones y reconoceréis el siguiente código, perteneciente al método Configure() de la clase Startup, que configura las rutas por defecto para los controladores MVC de una aplicación:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
En esta definición estamos estableciendo una férrea relación entre un patrón de ruta y la ubicación de los endpoints que procesarán las peticiones que encajen con el mismo. Estas definiciones, grabadas a fuego durante el arranque de la aplicación, dan poco margen para la sorpresa: por ejemplo, una petición hacia "/products/show" será procesada desde la acción "Show" del controlador "Products" inexorablemente.

Sin embargo, hay escenarios en los que necesitamos más flexibilidad y queremos ser nosotros los que seleccionemos el controlador y acción que procesará una petición, manipular los parámetros de ruta existentes o incluso añadir parámetros de ruta personalizados. Estas posibilidades son las que se abren con el rutado dinámico de ASP.NET Core 3.
Algunos probablemente habréis hecho este tipo de cosas mediante implementaciones personalizadas de IRouter: tened en cuenta este mecanismo ya no es compatible con endpoint routing y habría que migrarlo a este nuevo enfoque.

Transformaciones de valores de ruta

En la práctica, el rutado dinámico se implementa en clases transformadoras de valores de ruta, componentes personalizados que serán invocados en cada petición para darles la oportunidad de modificar a su antojo los parámetros de ruta.

Estas clases transformadoras deben heredar de DynamicRouteValueTransformer, una abstracción proporcionada por el framework que tiene la siguiente pinta:
public abstract class DynamicRouteValueTransformer
{
public abstract ValueTask<RouteValueDictionary> TransformAsync(
HttpContext httpContext,
RouteValueDictionary values);
}
Cuando heredemos de ella para crear nuestra transformación personalizada, el método TransformAsync() recibirá el objeto HttpContext y los valores de parámetros de ruta que han sido detectados hasta este momento en forma de diccionario. El objetivo de las transformaciones que implementaremos en la clase es alterar el contenido del diccionario de parámetros de ruta para que el comportamiento se adapte a nuestras necesidades, por ejemplo:
  • Si quisiéramos modificar la acción o controlador que procesaría una petición, sólo tendríamos que alterar el valor de los parámetros de ruta action o controller.
  • Podríamos añadir nuevos parámetros de ruta en función de los datos de entrada.
  • Podríamos reemplazar valores por otros, por ejemplo para traducirlo a una cultura neutra, des-sluggizarlo o transformarlo de alguna forma.
Una vez realizadas las modificaciones que consideremos sobre el diccionario de rutas, simplemente retornaremos una referencia hacia el mismo. Por tanto, el esquema de una implementación personalizada de DynamicRouteValueTransformer podría ser como el siguiente:
public class MyCustomTransformer : DynamicRouteValueTransformer
{
public override async ValueTask<RouteValueDictionary> TransformAsync(
HttpContext httpContext, RouteValueDictionary values)
{
// Modificamos la colección de parámetros de ruta, por ejemplo:
// values["controller"]="Products"
return values;
}
}
Para que una clase transformadora pueda ser utilizada, debemos registrarla previamente en el método ConfigureServices(). Podemos utilizar cualquier tipo de lifetime, por lo que somos libres de elegir el que más convenga:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<MyCustomTransformer>();
}
Fijaos que lo interesante de esto es que, dado que sus instancias son suministradas por el proveedor de servicios, podríamos utilizar inyección de dependencias en su constructor, :
public class MyCustomTransformer : DynamicRouteValueTransformer
{
private readonly IMyService _service;
public LocalizationTransformer(IMyService service)
{
_service = service;
}
public override ValueTask<RouteValueDictionary> TransformAsync(
HttpContext httpContext, RouteValueDictionary values)
{
// Usamos "service" para alterar los parámetros de ruta
}
}
Además, a la hora de registrar los endpoints tendremos que usar el extensor MapDynamicControllerRoute, indicar la clase DynamicRouteValueTransformer donde hemos implementado la lógica de transformación, y especificar el patrón de ruta:
app.UseEndpoints(endpoints =>
{
endpoints.MapDynamicControllerRoute<MyRouteTransformer>(
"{controller=Home}/{action=Index}/{id?}");
});
Nota: de la misma forma, podemos utilizar MapDynamicPageRoute() si lo que queremos es intervenir en las peticiones dirigidas a páginas Razor.

Ejemplo 1: localización simple de rutas

Veamos un ejemplo de DynamicRouteValueTransformer que implementa una sencilla transformación para traducir el controlador y acción a un idioma neutro, de forma que peticiones como "/products/view" o "/productos/ver" puedan ser procesadas desde ProductsController.View():
public class LocalizationTransformer : DynamicRouteValueTransformer
{
public override ValueTask<RouteValueDictionary> TransformAsync(
HttpContext httpContext, RouteValueDictionary values)
{
var controllerName = (string)values["controller"];
if ("productos".Equals(controllerName, StringComparison.InvariantCultureIgnoreCase))
{
values["controller"] = "products";
}
var actionName = (string)values["action"];
if ("ver".Equals(actionName, StringComparison.InvariantCultureIgnoreCase))
{
values["action"] = "view";
}
return new ValueTask<RouteValueDictionary>(values);
}
}
Aunque el ejemplo anterior es bastante simple porque está todo hardcodeado, obviamente podríamos crear soluciones bastante más potentes si tenemos en cuenta que la clase podría recibir dependencias y que en el método TransformAsync() tenemos toda la información del contexto disponible:
public class LocalizationTransformer : DynamicRouteValueTransformer
{
private readonly ILocalizationServices _loc;
public LocalizationTransformer(ILocalizationServices loc)
{
_loc = loc;
}
public override ValueTask<RouteValueDictionary> TransformAsync(
HttpContext httpContext, RouteValueDictionary values)
{
// Usar _loc para acceder a las traducciones:
// values["controller"] = await _loc.TranslateAsync(values["controller"]);
return values;
}
}

Ejemplo 2: selección dinámica del controlador

Supongamos que queremos que nuestra aplicación reciba un identificador de producto a través de una ruta con el patrón "/product/{id}/{action}" y que nos interesa que el controlador que procese estas peticiones dependa del tipo de producto (ya, sería un interés algo extraño, pero bueno...). Podríamos crear algo así:
public class ProductTypeTransformer : DynamicRouteValueTransformer
{
private readonly IProductRepository _productRepository;

public ProductTypeTransformer(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public override async ValueTask<RouteValueDictionary> TransformAsync(
HttpContext httpContext, RouteValueDictionary values)
{
if (int.TryParse(values["id"].ToString(), out var id))
{
var product = await _productRepository.GetByIdAsync(id);
if (product != null)
{
var controller = product.Type switch
{
ProductTypes.Laptops => "laptops",
ProductTypes.DesktopComputers => "desktop",
ProductTypes.Tablets => "tablet",
ProductTypes.Phones => "phone",
ProductTypes.Tv => "tv",
_ => "generic",
};
values["controller"] = controller;
}
}
return values;
}
}
Sencillo, ¿verdad? Como decía al principio, no es una feature para usarla todos los días, pero está bien saber que existe por si encontramos un escenario en el que pudiera venir bien esta flexibilidad para adaptar el routing a nuestras necesidades.

Publicado en Variable not found.

» Leer más, comentarios, etc...

Blog Bitix

Las aplicaciones integradas del entorno de escritorio GNOME

febrero 16, 2020 10:00

Todos los entornos de escritorio poseen unas pocas aplicaciones básicas y sencillas pero de uso muy común para todos los usuarios. Estas aplicaciones del entorno de escritorio están estrechamente integradas para ofrecer una experiencia de usuario consistente y funcionar correctamente entre ellas. GNOME posee varias desde un editor de archivos de texto, una calculadora, captura de pantalla, visor de imágenes, reproductor de música y vídeo, gestor de correo electrónico, calendario, navegador web, juegos, … y otras más.

GNOME

Todos los equipos informáticos incorporan un sistema operativo, este es una pieza importante e indispensable de software que abstrae las las especificidades de cada elemento hardware además de permitir se uso compartido desde la CPU, memoria, almacenamiento persistente, comunicación de red entre otros muchos componentes y periféricos que poseen los ordenadores actuales. Pero simplemente ofrece una interfaz de bajo nivel destinada a los programas no el usuario.

Para facilitar el uso el sistema operativo y en los actuales ofrecer una interfaz gráfica a través de ventanas, imágenes y texto a los usuarios están los entornos de escritorio. Los entornos de escritorio además incorporan una colección de aplicaciones de uso común para todos los usuarios como un navegador de archivos, navegador web, editor de texto, reproductor de música, reproductor de vídeo, visor de imágenes y documentos, gestor de aplicaciones, calculadora, capturador de pantalla entre las básicas.

Capas de software desde el hardware hasta el entorno de escritorio

Capas de software desde el hardware hasta el entorno de escritorio

Ejemplos de sistema operativos son GNU/Linux, Windows, macOS o FreeBSD. En los casos de Windows y macOS el mismo nombre engloba la interfaz gráfica y el entorno de escritorio sin ofrecer ninguna alternativa, en GNU/Linux hay varias alternativas de entorno de escritorio a elegir según las preferencias del usuario entre ellas GNOME, KDE, XFCE, Pantheon de elementary OS, MATE, Cinnamon o LXDE.

Las distribuciones de GNU/Linux proporcionan el conjunto de software completo formado por el núcleo o kernel que con el conjunto de aplicaciones de GNU forman el sistema operativo, el entorno de escritorio con sus aplicaciones básicas y finalmente aplicaciones adicionales que no forman parte del entorno de escritorio pero son preinstaladas en la instalación del sistema. Algunos ejemplos de distribuciones GNU/Linux son Ubuntu, Fedora, openSUSE, Debian, Arch Linux o elementary OS entre muchas otras.

Ubuntu openSUSE Debian

Arch Linux Fedora elementary OS

El entorno de escritorio se instala con el sistema con lo que primero es elegir una distribución GNU/Linux, para los usuarios que proviene de Windows o macOS y quieren probar GNU/Linux una distribución recomendable es Ubuntu. El siguiente paso seguir la guía de como instalar Ubuntu paso a paso desde cero y conocer las tareas básicas de administración y uso después de instalar una distribución. Para los usuarios que ya conocen GNU/Linux, quieren personalizar el sistema con sus preferencias no las de los desarrolladores de la distribución y prefieren un modelo de ciclo de vida rolling release en el que el software se mantiene en constante actualización con las últimas versiones puede instalar Arch Linux de forma rápida, desatendida y personalizable con un scipt.

El entorno de escritorio GNOME

GNOME con las críticas iniciales a la versión primera de la rama 3.0 publicada en abril del 2011 por el cambio significativo respecto a versiones anteriores ha mejorado mucho y sigue haciéndolo con cada nueva versión publicada cada seis meses.

En GNOME se opta por la simplicidad, a veces criticada por la falta de opciones de configuración y personalización, en la que los detalles gráficos y la usabilidad del sistema son aspectos con más relevancia respecto a versiones anteriores.

Continúan realizándose mejoras, algunas de las cuales son aplicadas en cada nueva versión y otras registradas para tenerlas en cuenta en futuras versiones.

Escritorio de GNOME Lanzador de aplicaciones de GNOME Lanzador de aplicaciones de GNOME

Entorno de escritorio y lanzador de aplicaciones de GNOME

Aplicaciones del entorno de escritorio GNOME

GNOME integra un conjunto de aplicaciones que proporcionan una funcionalidad importante en la experiencia del entorno de escritorio.

Estas aplicaciones están diseñadas por los propios diseñadores de GNOME como un paquete coherente, son parte de la experiencia GNOME, están diseñadas para funcionar de forma cooperativa unas con otras, tienen una integración fuerte con el entorno de escritorio, tienen nombres genéricos y son exclusivas de la experiencia GNOME.

El conjunto de aplicaciones del núcleo o esenciales de GNOME está formado por unas 30 agrupadas en diferentes categorías.

Conversaciones y organización personal

  • Evolution: cliente de correo electrónico y organizador
  • Geary: cliente de correo electrónico
  • Contactos
  • Calendario

Evolution Geary

Contactos Calendario Calendario

Archivos

  • Visor de imágenes
  • Fotos
  • Documentos
  • Gestor de archivos (compresor)
  • Música
  • Lollypop: reproductor de música
  • Vídeos

Visor de imágenes Fotos Fotos

Documentos Archivos

Música Música Música

Lollypop

Vídeos Vídeos Vídeos

Herramientas de sistema

  • Captura de pantalla
  • Discos
  • Ayuda
  • Trazas
  • Informar de problema
  • Terminal
  • Uso (system monitor + disk usage)

Captura de pantalla Discos Ayuda de GNOME

Terminal Terminal Monitor del sistema

Sistema esencial

  • Configuración: opciones de configuración del sistema
  • Software: instalar, actualizar y desinstalar programas de software
  • Web: navegador de páginas web

Configuración Configuración Configuración

Software Software

Web Web Web

Mundo

  • Relojes
  • Tiempo
  • Mapas

Relojes Tiempo

Mapas Mapas Mapas

Mapas

Utilidades

  • Calculadora
  • Tipografías
  • Notas
  • Editor de texto
  • Retoques: ofrece varios opciones de personalización
  • Herramientas de red

Calculadora Calculadora

Tipografias Tipografias

Notas Editor de texto Retoques

Herramientas de red

Propósito especial

  • Cajas: virtualización

Cajas Cajas Cajas

Audio y vídeo

  • Cheese: aplicación webcam
  • Sound Juicer: extractor de CD de audio
  • Grabadora de sonido

Cheese Grabadora de sonido

Creación y edición

  • Brasero: grabadora de CD y DVD
  • EasyTAG: editor de metadados de archivos de música
  • Subtítulos: editor de subtítulos para archivos de vídeo

Comunicación

  • Fractal
  • Polari

Entornos de desarrollo

  • Builder: entorno integrador de desarrollo
  • Glade: diseñador de interfaces gráficas de GNOME

Builder Builder Builder

Builder Glade Glade

Juegos

Aventura
  • MUD
  • Arcade
  • Nibbles
  • Robots
  • Blocks
  • Quadrapassel (Lines)
  • Board
  • Chess
  • Iagno
  • Mahjongg
Cartas
  • Aisleriot (Solitaire, sol)
  • Tali
Emuladores
  • Juegos: emulador de juegos
Lógica
  • 2048
  • Atomix
  • Five or more
  • Four in a row
  • gbrainy
  • Hitori
  • Klotski
  • Lights off
  • Mines
  • Sudoku
  • Swell Foop
  • Taquin
  • Tetravex

Juegos

Ajedrez Klotski Minas

Sudoku Swell Foop Tetravex

Formatos de vídeo y audio y aplicaciones de terceros

Para que las aplicaciones de vídeos y música soporten más formatos de archivo es necesario instalar los decodificadores de esos formatos. En Arch Linux los siguientes paquetes.

Códecs no encontrados en Vídeos

1
$ sudo pacman -S gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav
pacman-gstreamer.sh

Las aplicaciones de terceros proporcionan funcionalidades adicionales, son una alternativa a las de GNOME con más funcionalidades o complementan a estas que algunos usuarios necesitan como paquete ofimático con procesador de documentos, hoja de cálculo y presentaciones, edición de vídeo, editor de fotos, editor de imágenes vectoriales, editor de animación 3D, gestor de biblioteca de libros electrónicos, descargas de archivos, mensajería instantánea, videoconferencia, nube privada, otro reproductor de audio y vídeo, conversores entre formatos de audio y vídeo, compiladores, bases de datos, gestor de contraseñas, …

En otro artículo recojo un listado de programas básicos según categoría en GNU/Linux de terceros que no son específicos de ningún entorno de escritorio. Todos esos programas son software libre sin coste de licencia de uso para cualquier propósito incluyendo que sea personal o empresarial con fines lucrativos.

» Leer más, comentarios, etc...

Blog Bitix

El recolector de basura de Java, que hace y como funciona en cada versión

febrero 15, 2020 12:00

El recolector de basura o garbage collector es una de las piezas fundamentales del lenguaje Java y su plataforma. Una ventaja sobre lenguajes que no poseen reflectores de basura y que se ha adoptado por los lenguajes desarrollados en la últimas décadas. Facilita a los programadores la creación de programas, una mayor productividad, evita errores y fallos de seguridad.

Java

Den entre las características de Java ¿a qué se debe su popularidad? ¿al lenguaje simple de fácil lectura sin crípticas expresiones? ¿a ser multiplataforma, write once, run everywhere? ¿a la máquina virtual JVM que lo hace independiente de la plataforma sistema operativo y soporta múltiples lenguajes compilados a bytecode? ¿a su extensa y completa documentación Javadoc de cada clase incluida en el JDK? ¿a las clases incluidas en el JDK con una completa librería para trabajar con colecciones, interfaces gráficas o conexión a bases de datos? ¿a mantener la compatibilidad hacia atrás de modo que programas escritos hace 20 años sigan compilando y funcionando en versiones más recientes de la máquina virtual?

Además de todas las anteriores entre las principales hay que añadir la recolección de basura que libera al programador la gestión de la memoria, tanto para solicitarla como para devolverla al sistema. Lenguajes más antiguos y con otros propósitos más cercanos a la programación de sistema donde prima el rendimiento y el acceso cercano al hardware como C no poseen recolector de memoria y requieren que el programador solicite de forma explícita con la función malloc la memoria y el tamaño de la memoria a reservar y la libere también cuando se deja de usar de forma explícita con la llamada a la función free.

Esto para cada dato y en un programa grande serán muchos supone una dificultad añadida a la creación y mantenimiento. Este ejemplo en código C muestra el uso de la función malloc con la que el programa solicita memoria al sistema operativo y con free la libera.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *ptr;

    // allocate memory
    ptr = (int*) malloc(1 * sizeof(int));

    // if memory cannot be allocated
    if(ptr == NULL)
    {
        printf("Error! memory not allocated.");
        exit(0);
    }

    printf("Enter a number: ");
    scanf("%d", ptr);

    // print the number
    printf("Number = %d\n", *ptr);
    
    // deallocating the memory
    free(ptr);

    return 0;
}
main.c
1
2
3
4
5
[picodotdev@archlinux ~]$ gcc main.c -o main
[picodotdev@archlinux ~]$ ./main
Enter a number: 7
Number = 7
[picodotdev@archlinux ~]$
main.out

Como desarrollador de Java apenas hay que preocuparse de fugas de memoria ni de fallos en el programa por liberar memoria antes de que dejar de usarla. En Java la solicitud de memoria al sistema se hace de forma explícita con la palabra reservada new para crear una instancia de un objeto pero no hace falta especificar el tamaño de la memoria a reservar como en C. Tampoco hace falta liberar de forma explícita el objeto cuando dejar de usarse es el propio recolector de basura el que determina si una instancia ha quedado inaccesible desde el programa y lo libera en el proceso de recolección de basura que ejecuta la máquina virtual de forma periódica y automática sin la intervención del programa.

El recolector de basura además de simplificar el código de las aplicaciones, evita fallos en tiempo de ejecución con posibilidad de que sean difíciles de depurar, evita en gran medida las fugas de memoria y fallos graves de seguridad. En los programas en C es muy común errores de seguridad por casos en los que se sobreescriben zonas de memoria contiguas por no hacer comprobaciones en los límites de los arrays, muchos boletines de seguridad CVE en muchas librerías tienen un origen de este tipo. En Java si se intenta acceder a un array fuera de sus límites se produce una excepción ArrayIndexOutOfBoundsException, el programa sigue teniendo un error pero no tiene por que terminar su funcionamiento de forma drástica porque el sistema operativo lo mata y no son posibles los fallos de seguridad por sobreescribir una zona de memoria contigua al array pero fuera de sus límites.

La desventaja de los recolectores de basura es que cada cierto tiempo requieren detener la ejecución de la aplicación para proceder a liberar la memoria dejada de usar por la aplicación. Estas pausas que suceden fuera del control de la aplicación hace que para entornos donde se necesite una respuesta bajo unos términos de tiempo bajos o extremadamente alto rendimiento como en el caso de sistemas en tiempo real hace que los recolectores de basura sean una dificultad.

En Java una de las áreas para mejorar el rendimiento y tiempo de respuesta de las aplicaciones es modificar el algoritmo de recolección de basura, para mejorar el tiempo que necesita para ejecutarse y número de pausas además de posibilitar el paralelizar la ejecución del recolector de basura con la ejecución de la aplicación. A lo largo de los años en Java ha habido varios recolectores de basura.

Salvo casos en los que hay que ajustar al límite la máquina virtual en aplicaciones que necesitan gran rendimiento no es necesario preocuparse por el funcionamiento del recolector de basura, hace su cometido como se espera. En mis años de experiencia nunca he tenido que configurarlo, pero es interesante conocer que mejoras se van implementando en cada nueva generación de algoritmo. En la mayoría de los casos parece que el sucesor se basa en el anterior y aporta alguna mejora.

En la revista JavaMagazine se han publicado varios artículos explicando el recolector de basura de Java. En las secciones de las diferentes versiones de recolectores de basura resumo parte del contenido de esos artículos.

Otros artículos relativos a la recolección de basura y su configuración son los de la Garbage Collection Tuning con una explicación más detallada.

Cómo funciona el recolector de basura

En un lenguaje orientado a objetos como Java los datos están contenidos en los objetos. Los objetos son almacenados en el espacio de memoria del sistema denominado heap distinta a la memoria del código ejecutable del programa, datos para las constantes y de las pilas de memoria para los argumentos y valores de retorno entre métodos.

Las clases de colecciones de Java contienen referencias a objetos. Un ejemplo podría ser el siguiente de un árbol binario.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class TreeNode {

    private TreeNode left;
    private TreeNode right;
    private int data;

    TreeNode(TreeNode l, TreeNode r, int d) {
        this.left = l;
        this.right = r;
        this.data = d;
    }

    public void setLeft(TreeNode l) { 
        left = l;
    }

    public void setRight(TreeNode r) {
        right = r;
    }
}
TreeNode.java

Al insertar nodos todos los objetos insertados están accesibles.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
...

public class Main {

    public static void main(String[] args) {
        TreeNode left = new TreeNode(null, null, 13);
        TreeNode right = new TreeNode(null, null, 19);
        TreeNode root = new TreeNode(left, right, 17);
        ...
        root.setRight(new TreeNode(null, null, 21));
    }
}
Main.java

Objetos al inicializar la estructura de datos

Objetos al inicializar la estructura de datos

Al realizar la operación de eliminación de un nodo del árbol el objeto eliminado del árbol deja de ser accesibles para el programa sino hay más referencias en otras estructuras de datos con la que alcanzar a ese objeto y pasa a ser reclamable por el recolector de basura.

Objetos después de eliminar un nodo

Objetos después de eliminar un nodo

Con más operaciones los objetos no accesibles aumentan. Estos objetos no accesibles siguen consumiendo memoria, el recolector de basura se encarga de liberar la memoria de esos objetos y después compactar la memoria en uso y la liberada queda utilizable para nuevas instancias de objetos.

Objetos después de realizar múltiples operaciones de inserción

Objetos después de realizar múltiples operaciones de inserción

Al realizar la operación de compactar la memoria los objetos cambian de ubicación y el programa debe conocer la nueva ubicación, esto requiere actualizar las referencias de los objetos almacenados en las estructuras de datos. La forma fácil de realizar la liberación de memoria y la compactación es parar los threads de la aplicación, liberar la memoria, compactarla y actualizar todas las referencias de los objetos a la nueva ubicación, después reiniciar la aplicación. Esta parada de la aplicación se conoce como stop-the-world. Sin embargo, el parar la aplicación reduce el rendimiento, esto no es deseable.

Para reducir las pausas de los recolectores de basura hay dos estrategias:

  • Los algoritmos concurrentes: realizar el trabajo mientras funciona la aplicación, la aplicación no necesita pausas ni sufre pérdida de rendimiento.
  • Los algoritmos paralelos: emplear más threads para hacer el trabajo más rápido, aumenta el rendimiento del recolector de basura.

El recolector de basura por defecto en Java 8 usar la estrategia paralela, usa varios threads para tener un alto rendimiento. Otras versiones de algoritmos emplean ambas técnicas simultáneamente para tener un alto rendimiento y apenas sin pausas. Hay dos áreas de mejora en los algoritmos de recolección de basura y medir su desempeño. La primera es el rendimiento, cuanta cantidad de tiempo de CPU de la aplicación es gastada en realizar recolección de basura en vez de ejecutar código de la aplicación. La segunda es el tiempo de latencia en las pausas.

Recolector de basura Parallel

El recolector de basura parallel emplea zonas para segregar los objetos, la zona de objetos jóvenes y la zona de objetos viejos. Inicialmente los objetos se crean en la zona de objetos jóvenes, cuando han sobrevivido a varios ciclos del recolector de basura son movidos a la zona de objetos viejos.

La razón es que en vez de recolectar los objetos de toda la memoria hay más probabilidad de recolectar más objetos que han dejado de usarse en la zona de objetos jóvenes. Eventualmente también es necesario recolectar los objetos de la zona de objetos viejos.

Es el recolector de basura por defecto en Java 8 y anteriores. La opción para usar este recolector de basura es la siguiente.

1
-XX:+UseParallelGC
java-option-gc-parallel.txt

Recolector de basura Garbage First o G1

El recolector de basura G1 usa ambas estrategias la paralela y la concurrente. Usa threads concurrentes mientras la aplicación está funcionando buscando los objetos vivos y usa la estrategia paralela para realizar la recolección y compactación rápidamente manteniendo las pausas bajas.

El recolector de basura G1 también divide la memoria en regiones de memoria catalogadas como de objetos jóvenes y objetos viejos. Las regiones de objetos jóvenes las recolecta en cada pausa, para las zonas de objetos viejos tiene cierta flexibilidad para recolectar muchas o pocas como la estimación de tiempo que le llevará hacerlo le permita para cumplir con el objetivo de tiempo de pausa configurado dado que permite ajustar según preferencia el límite de tiempo máximo deseado para las pausas.

División por zonas de G1 y compactación de objetos

División por zonas de G1 y compactación de objetos

G1 conoce cuantos datos vivos hay en cada región, lo calcula con la estrategia concurrente mientras la aplicación está funcionando, y el tiempo aproximado que consume copiar los datos vivos. Si se prefieren pausas bajas por el tiempo de pausa configurado G1 puede elegir evacuar solo unas pocas regiones. Si las pausas pueden ser mayores G1 puede elegir mayor número de regiones. Esta flexibilidad le permite a G1 liberar primero las zonas de objetos viejos en las que estime que liberará más objetos dado que conoce cuantos objetos siguen vivos.

La contrapartida de especificar pausas bajas es que G1 puede no ser capaz de mantener el ritmo de liberación de memoria, en cuyo caso eventualmente opta por parar la aplicación con el modo stop-the-world. Esto implica que el proceso de búsqueda de objetos vivos y el proceso de copiado es realizando mientras los threads de la aplicación están parados. Si G1 no puede cumplir con el objetivo de tiempo de pausa en recolecciones parciales, entonces el recolector de basura necesitará una pausa de mayor tiempo que el límite máximo deseado especificado.

G1 en general es un recolector con un buen balance entre rendimiento y restricciones de tiempo de pausa. Es el recolector de basura por defecto en Java 9.

1
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
java-option-gc-g1.txt

Recolector de basura Shenandoah

Usa la misma disposición de regiones que G1 y usa el mismo sistema de escaneo concurrente para calcular la cantidad de objetos vivos en cada región. Difiere en que la compactación también es concurrente, de modo que no necesita limitar el número de regiones a recolectar para minimizar los tiempos de las pausas.

La dificultad para Shenandoah es que la copia concurrente se realiza al mismo tiempo que los threads de la aplicación están accediendo al objeto de modo que ambos deben estar de acuerdo en donde está el objeto. La dirección del objeto puede estar en otros varios objetos y la actualización debe realizarse simultáneamente.

La solución que aplica es una indirección. Los objetos son reservados en memoria con espacio extra para un puntero de indirección. Cuando los threads de Java acceden al objeto leen primero el puntero de indirección para ver donde se ha movido el objeto. Cuando el recolector de basura mueve el objeto, actualiza el puntero de indirección a la nueva localización. Los objetos nuevos tienen un puntero de indirección que apunta a si mismos. Solo cuando el objeto es copiado durante la recolección de basura el puntero de indirección apunta otro sitio. Si el programa Java modifica los datos de un objeto que Shenandoah está copiando, se produce un problema de concurrencia que es solventado haciendo que los threads de la aplicación cooperen con los threads del recolector de basura.

Shenandoah elimina la necesidad de realizar pausas durante la compactación de modo que las pausas cuando se hacen son mucho menores. El recolector de basura Shenandoah es un proyecto de OpenJDK que forma parte del OpenJDK 12 y está siendo portado al JDK 8 y 11. Se puede activar en Java 12 con la siguiente opción de la máquina virtual.

1
-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
java-option-gc-shenandoah.txt

Recolector de basura ZGC

Para los algoritmos que realizan pausas incrementar la memoria heap mejora el rendimiento dado que el número de pausas es menor por la menor necesidad de liberar memoria pero hace que las pausas sean más largas porque hay más trabajo que realizar dado que la memoria total es mayor.

Los objetivos principales de ZGC son baja latencia, escalabilidad y facilidad de uso. Para conseguirlo todas las operaciones de recolección de basura se realizan de forma concurrente mientras la aplicación continúa ejecutándose salvo algunas excepciones. Escala desde unos cientos de megabytes de memoria a memorias de tamaño de terabytes manteniendo consistentemente tiempos bajos de pausas menores de entre 10 y 2 ms.

Los recolectores de basura anteriores y hasta ahora necesitaban realizar pausas stop-the-world para algunas operaciones de recolección de basura. Para un recolector de basura de baja latencia esto es problemático de modo que ZGC realiza todas las operaciones concurrentemente a la aplicación de modo que no hay apenas latencias.

Comparación de latencia entre ZGC, Parallel y G1

Comparación de latencia entre ZGC, Parallel y G1

ZGC se puede activar en Java 13 con la siguiente opción para la máquina virtual.

1
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
java-option-gc-zgc.txt

» Leer más, comentarios, etc...

Navegapolis

Por qué pusimos en marcha un registro de propiedad intelectual

febrero 13, 2020 06:47

safe creative cc tech summit

En 2007, cuando pusimos en marcha Safe Creative, muchos expertos dudaban si era posible abrir en internet un registro de propiedad intelectual.

El mismo Creative Commons nos invitó en el  Technology Summit de 2008,  a explicar "por qué y para qué un registro de propiedad intelectual", presentándonos con un título que parecía cuestionarlo: "Developers of digital copyright registries and similar animals" :-P 

¿Por qué un registro electrónico? ¿Por qué no seguir registrando como siempre en oficinas de registro de ministerios o consejerías de educación?

Los registros de propiedad intelectual no otorgan derechos: el autor de una obra creativa tiene todos sin necesidad de registrarlos (Convenio de Berna). Lo que ocurre es que resulta muy aconsejable que registre su obra antes de "moverla", para tener asentada una primera prueba declarativa de su autoría, para publicar su trabajo o enviar versiones previas con tranquilidad, sabiendo que dispone de la merjor prueba en el tiempo frente a quien pudiera estar tentado de atribuírselo.

Y así fue. Para subir a internet la canción que hemos compuesto, el vídeo, el libro.... con la seguridad de haberlo registrado previamente, sin tener que esperar a que abra la ventanilla del registro a las 9 de la mañana para llevarles  una copia. Para eso nació Safe Creative (y para otras muchas cosas que tampoco se pueden hacer en un registro tradicional).

... que preguntárselo hace 13 años era normal, ¡pero a estas alturas!     ;-)

 

 EN EL REGISTRO TRADICIONALEN SAFE CREATIVE
     
La prueba de derechos de autoría se basa En la presunción administrativa de veracidad que la ley del país correspondiente concede al funcionario que inscribe la manifestación del autor. En la evidencia tecnológica que constituye la identificación de la obra con 3 huellas criptográficas diferentes, y de la fecha por la aplicación de un sellado de tiempo cualificado, redundado con un proceso de auditoría diaria sobre blockchain. 
     
Validez internacional La presunción de veracidad del funcionario es válida en su propio país. La peritación de pruebas o evidencias tecnológicas es válida en todas las jurisdicciones.
     
Operativa Normalmente presencial, con formatos de obras en algunos casos definidos y restringidos por reglamentos internos.

On line 24 x 7 en cualquier formato que permita identificar a la obra.

 

» Leer más, comentarios, etc...

Coding Potions

Vue slots ≫ Qué son y cómo usarlos

febrero 13, 2020 12:00

Introducción ¿Qué son los slots?

Los slots son un mecanismo de Vue JS que sirve para insertar contenido HTML dentro de los componentes. Es decir, con los props puedes pasar objetos y variables javascript a los componentes y con los slots puedes insertar contenido HTML dentro de otros componentes.

Imagina que quieres crear un componente que sirva para renderizar un header, y además quieres que dentro del header, a la derecha se puedan poner botones o otra información de forma que cambien dependiendo de la página. Esto se puede hacer con props, pero con los slots es mucho más sencillo y encima permites que desde fuera puedes pasar el contenido HTML que quieras.

Los slots sirven para insertar contenido en el componente hijo

Seguramente estés confuso, pero ahora con los ejemplos lo vas a ver mucho más claro.

Cómo crear slots en Vue

Sigamos con el ejemplo del botón que vimos en el artículo anterior sobre props y eventos en Vue. Imagina que ahora quieres poder añadir un icono que sea personalizable para cada botón. Una forma de abordarlo es creando un prop para pasar desde fuera el nombre del icono. Otra forma de hacerlo es con slots:

<template>
  <button @click="handleClick">
    <slot></slot>
  </button>
</template>
export default {
  methods: {
    handleClick() {
      this.$emit("click", this.example);
    }
  }
}
<script>
</script>

El slot es una etiqueta especial que tiene Vue. Cuando pones un slot lo que estás diciendo es que en ese punto vas a colocar contenido desde fuera. Fíjate que ahora no necesitamos poner un prop, porque todo el contenido que vaya dentro del botón se pasará desde fuera.

Veamos ahora cómo pasar contenido a los slots:

<template>
  <my-button @click="handleClick">
    <i class="fas fa-cat"></i>
    Botón de ejemplo
  </my-button>
</template>

import MyButton from "@/components/MyButton.vue";

export default {
  components: {
    MyButton
  },
  methods: {
    handleClick(info) {
      console.log("Click event on the button of the children with: " + info)
    }
  }
}
<script>
</script>

Todo el contenido que pongas dentro de la etiqueta HTML de un componente con slot se sustituirá dentro del componente en el lugar en el que esté colocada la etiqueta slot. Para el ejemplo anterior, el botón finalmente quedará como:

<button>
  <i class="fas fa-cat"></i>
  Botón de ejemplo
</button>

Pero no todo es bueno con los slots. La parte mala de los slots es que das demasiada libertad a la hora de usar el componente. Por ejemplo para el caso del botón, alguien podría poner dentro del slot una tabla por ejemplo haciendo que el botón se vea mal. Para este caso en concreto yo usaría un prop para el texto del botón y otro prop para el icono.

Más de un slot. Named slots

Otra cosa que se puede hacer con Vue es añadir más de un slot, para ello vas a tener que colocar un nombre a cada slot para poder indentificarlos:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

Con el atributo name le pones el nombre a cada slot. Para poder elegir qué poner en cada slot tienes que usar una etiqueta de Vue llamada template.

Los templates son unas etiquetas especiales de Vue que cuando se compila la página se eliminan dejando solo su contenido. En el ejemplo de abajo se colocará el contenido definido para cada slot en su sitio del componente eliminándose las etiquetas template y solo quedando los headings y los párrafos

<base-layout>
  <template slot="header">
    <h1>Header de la página</h1>
  </template>

  <p>Contenido de la página</p>

  <template slot="footer">
    <p>Footer de la página</p>
  </template>
</base-layout>

El ejemplo de arriba finalmente quedará renderizado como:

<div class="container">
  <header>
    <h1>Header de la página</h1>
  </header>
  <main>
    <p>Contenido de la página</p>
  </main>
  <footer>
    <p>Footer de la página</p>
  </footer>
</div>

Las etiquetas template han desaparecido. El slot del medio, el del contenido, no tiene nombre y por lo tanto será sustituido en el slot por defecto sin nombre.

Contenido por defecto del slot

También puedes poner contenido por defecto en caso de que no uses el slot:

<button type="submit">
  <slot>Submit</slot>
</button>

En caso de que al crear este componente botón no le pases contenido, Vue lo creará con el texto Sumbit.

Con esto puedes crear todo el contenido que quieras dentro de los slots por si al usar el componente no pasas nada dentro.

Scoped slots

Los scoped slots es de esas cosas que no los conoce mucha gente. Lo que permiten los scoped slots es poder pasar información desde el componente hijo al padre, es decir, desde el hijo pasas contenido al padre para que éste lo pueda pintar como necesite.

Ponte en el ejemplo de que tienes un componente que pinta una lista de usuarios. A este componente le pasas un prop con el array de usuarios a pintar. Pues bien, con los scoped slots, al hacer el v-for para pintar los usuarios, puedes pasar al componente padre, en el slot, el usuario que se está pintando en ese momento de tal forma de que en el padre decides cómo lo quieres pintar. Por ejemplo:

En el componente hijo:

<ul>
  <li v-for="(user, i) in users" :key="i">
    <slot v-bind:user="user"></slot>
  </li>
</ul>

Dentro del slot haces bind de la variable user que se está pintando para pasarla al componente padre.

En el componente padre:

<user-list>
  <template v-slot:default="slotProps">
    <span>{{ slotProps.user.firstName }}</span>
    <span>{{ slotProps.user.lastName }}</span>
  </template>
</user-list>

Recoges la variable del user en slotProps y pintas el usuario como necesites.

Por ejemplo, usando el mismo componente, también podrías tener a la vez en otro componente:

<user-list>
  <template v-slot:default="slotProps">
    <div>First name: </div>
    <div>{{ slotProps.user.firstName }}</div>
    <div>Last name: </div>
    <div>{{ slotProps.user.lastName }}</div>
  </template>
</user-list>

Esto es muy útil cuando quieres tener un componente de tabla o un componente que pinte una lista o colección de elementos y necesitas dos formas diferentes de pintarlos. Recogiendo el valor en los componentes padres puedes decidir cómo vas a querer renderizarlo usando un solo componente hijo.

Conslusiones

Como he dicho, yo personalmente recomiendo crear los componentes usando props y no slots porque así limitas más el contenido de los componentes para que no queden cosas extrañas. De todas formas para ciertos componentes sobre todo estructurales en los que necesitas pasar mucho contenido HTML su uso si que está más justificado.

En próximos capítulos veremos como estructurar los proyectos de Vue para poder mandar peticiones HTTP a un servidor o API y así poder conectar a las aplicaciones creadas en Vue con un servidor con bases de datos.

» Leer más, comentarios, etc...

Fixed Buffer

Escribiendo código de alto rendimiento en .Net Core

febrero 11, 2020 09:00

Tiempo de lectura: 12 minutos
Imagen ornamental para la entrada Escribiendo código de alto rendimiento en C# con .Net Core

Recientemente he estado en una charla de un grande del sector como es Carlos Landeras y en el clásico afterwork tras la charla, he tenido la oportunidad de hablar con el largo y tendido con el sobre el tema y finalmente me he decidido a escribir una entrada sobre cómo hacer código de alto rendimiento utilizando .Net Core.

En primer lugar y, ante todo, hay que utilizar la cabeza y no perder tiempo en optimizar sin ser necesario. La gran mayoría de aplicaciones no necesitan de utilizar las cosas que vamos a ver a continuación, pero es conveniente saber que existen porque a veces hacen falta.

¿Cuándo debo mejorar el rendimiento de mis aplicaciones .Net Core?

Lo primero que cabe preguntarse en este momento es si vale la pena optimizar nuestra aplicación, ¿Y cómo puedo decidir si vale la pena? La pregunta es difícil de resolver porque no hay una verdad absoluta al respecto, pero principalmente podemos hacernos algunas preguntas para decidir si necesitamos modificar nuestro código:

  • ¿Tiene una alta concurrencia?
  • ¿Los recursos del equipo son limitados?
  • ¿Estamos teniendo problemas con el rendimiento?
  • ¿Estamos creando una librería cuyo uso pueda caer en algún escenario de los anteriores?

Optimizar un código tiene un coste de desarrollo extra y muchas veces no vale la pena el esfuerzo necesario para conseguirlo. No es lo mismo optimizar un método que se ejecuta una vez al arrancar nuestra aplicación, por ejemplo, que un método que se llama cientos de veces por segundo. Si estamos ejecutando nuestra aplicación en un servidor tampoco es lo mismo que, si por el contrario la estamos ejecutando en una raspberry pi por ejemplo, los recursos en un caso son ‘ilimitados’ y en el otro no. A pesar de lo anterior… ¿Estamos teniendo problemas? Podemos tener un código que se ejecuta cientos de veces por segundo en un equipo con pocos recursos, pero aun, así no existir problemas en el rendimiento.

Si por el contrario eres de los que sí necesita optimizar tu código, agárrate que vienen curvas (aunque voy a intentar simplificarlo al máximo).

¿Cómo puedo saber dónde mejorar el rendimiento de mis aplicaciones .Net Core?

La respuesta a esta pregunta es muy variada y depende de que es exactamente lo que haga el código que queremos optimizar. Por ejemplo, ¿cúal de los siguientes métodos es mejor para concatenar dos cadenas?

public string StringConcat() => string.Concat(_stringA, _stringB);

public string StringFormat() => string.Format("{0}{1}", _stringA, _stringB);

public string StringInterpolation() => $"{_stringA}{_stringB}";

public string StringAdd() => _stringA + _stringB;

public string StringBuilder()
{
    var builder = new StringBuilder();
    builder.Append(_stringA);
    builder.Append(_stringB);
    return builder.ToString();
}

Siempre hemos oído que hacer una operación ‘+’ entre dos cadenas es lo peor que podemos hacer ya que las cadenas son inmutables y es una mala práctica, que es mejor utilizar un StringBuilder pero… ¿Serías capaz de decir de las 5 opciones cual es mejor y cual peor sin ser meras suposiciones?

Para poder optimizar cualquier parte del código, lo primero es medirlo. Si no tomamos métricas sobre los recursos que está utilizando un código, todo lo que hagamos serán meras suposiciones. Precisamente para temas como este, hace unos meses hablábamos sobre como medir el rendimiento de nuestro código utilizando BenchmarkDotNet y hoy vamos a recuperar la utilidad que ofrece para hacer precisamente eso, medir. No es posible optimizar código sin hacer diferentes mediciones que nos digan si estamos haciendo que las cosas vayan mejor o peor. Por ejemplo, sin medir nada, podemos pensar que lo mejor tal vez sea concatenar (el método StringConcat) o tal vez usar un StringBuilder, y que el peor quizás sea hacer una simple suma (el método StringAdd). Cuando hacemos un benchmark de esos cinco métodos, los resultados tal vez sorprendan a más de uno…

La imagen muestra los resultados para el código anterior donde los resultados dicen que StringConcat tarda 23 ns, StringFormat 118 ns, StringInterpolation 24 ns, StringAdd 23 ns y StringBuilder 49 ns,

Sin ánimo de venir a decir ahora que hacer suma de cadenas ya no es un problema (que sí lo es), lo que pretendo mostrar es que siempre es necesario medir para poder determinar dónde está el problema. En el caso anterior al ser solo dos cadenas y una única repetición, el caso de sumar cadenas no tiene impacto mientras que otros métodos pueden costar hasta 5 veces más, si estuviésemos haciendo esto en ciclos largos (por ejemplo 1000 veces) donde se haga la cadena A unida a la B y el resultado a la B evidentemente los resultados serían muy distintos:

La imagen muestra los resultados para el código anterior repetido 1000 veces donde los resultados dicen que StringConcat tarda 648 ns, StringFormat 1922 ns, StringInterpolation 616 ns, StringAdd 610 ns y StringBuilder 11 ns,

En este caso, solo nos hemos fijado en el tiempo de ejecución, pero… ¿Y la memoria? ¿Puede una mala gestión de memoria ser un problema que nos haga perder rendimiento en una aplicación .Net Core? La verdad es que sí. Por cómo trabaja .Net en general, la memoria la maneja una herramienta conocida como el Garbage Collector.

¿Cómo funciona el Garbage Collector en .Net Core?

El recolector de basura es una herramienta que utilizan lenguajes como C# o Java para gestionar la memoria de la aplicación. Básicamente el recolector se encarga de hacer las reservas de memoria cuando creamos un nuevo objeto por referencia. El concepto de la gestión de memoria en .Net es un tema complejo y muy interesante y saber sobre él nos permite generar código con mucho mejor rendimiento.

La memoria se divide en dos secciones diferentes con diferentes finalidades.

  • Stack (Pila)
  • Heap (Montón)

La pila es el lugar donde se almacenan las variables por valor (struct) mientras que el montón es donde se almacenan las variables por referencia (class).

Esto no es siempre así como plantearemos más adelante pero de momento podemos asumir que sí.

La pila es ‘reducida’ pero muy rápida y no es manejada por el recolector de basura mientras que el montón es más grande y si lo gestiona el recolector de basura y tiene una manera muy especial de hacerlo. De cara a optimizar el proceso de limpieza, .Net divide los objetos del montón en 3 generaciones, siendo la generación 0 la de los objetos más efímeros y la generación 2 la de los objetos más longevos de la aplicación.

El recolector de basura es quien se encarga de asignar la memoria cuando creamos nuevos objetos por referencia, pero no siempre tiene esa memoria disponible. Cuando no tiene suficiente memoria libre para darnos la que necesitamos, iniciará una recolección de la generación 0 que consiste en limpiar los objetos que ya no use usan y subir de generación los que sí se usan.

Después de hacer este trabajo, pueden pasar dos cosas, o ya hay memoria suficiente o no la hay. En caso de que la haya, nuestra aplicación sigue funcionando normalmente pero si no había memoria suficiente, lo que va a hacer el recolector es el mismo proceso con la generación 1. Por último, si la generación 1 tampoco es suficiente, hará lo mismo con la 2. La diferencia es que en caso de la generación 2, los objetos siguen en la generación 2.

Vale, llegados a este punto me vas a decir: `¡¡Menuda brasa!! ¿Y esto de qué me vale?`(Con razón avise de que venían curvas). Es importante saber cómo funciona el garbage collector porque, aunque su existencia nos facilita mucho la vida, su trabajo es más prioritario que el nuestro. Esto quiere decir que cada vez que el recolector hace una recolección nuestra aplicación se va a parar mientras dure la recolección. (Por suerte esto tampoco es así al 100% pero asumamos que sí).

Cuanto más trabaje el recolector de basura, peor rendimiento tendrá nuestra aplicación. En las mediciones del código que hemos hecho antes, solo estábamos mostrando el tiempo de ejecución, pero vamos a hacer lo mismo añadiendo la memoria:

La imagen muestra los resultados con memoria para el código anterior donde los resultados dicen que StringConcat consume, 4921 KB StringFormat 14899 KB, StringInterpolation 492 1KB, StringAdd 4921 KB y StringBuilder solo 26 KB

¿Cómo reduzco el consumo de memoria de mi aplicación?

En este punto tenemos claro que utilizar memoria de cualquier manera es algo que puede afectar negativamente al rendimiento y nosotros lo que queremos es precisamente mejorar el rendimiento de una aplicación .Net Core. Para eso .Net Core nos ofrece ciertas herramientas para poder hacer código ‘memory friendly’.

Para esto tenemos varias opciones built-in que nos van a permitir mejorar y optimizar las reservas de memoria, consiguiendo así ponerle las cosas más fáciles al recolector.

Devolver valores por referencia en vez de nuevas copias para mejorar el rendimiento de aplicaciones .Net Core

Acabamos de plantear que el hecho de instanciar clases tiene un coste para el recolector de basura, pero utilizar instancias de structs tiene también su personalización. Esto es porque al ser objetos por valor, cada vez que pasemos el dato como parámetro o retorno de un método, realmente estaremos pasando una copia completa de la estructura… En este caso no son los problemas con el recolector, sino la sobrecarga de copiar datos dentro de la pila lo que pretendemos mejorar. Imagina una estructura con varios campos, el hecho de que tengamos un método que retorne una copia o una referencia puede cambiar significativamente el tiempo cuando es un código ‘caliente’ por el que se pasa miles de veces. Los resultados de llamar a este código 1000 veces ya tienen una diferencia del 30%.

private BigStruct CreateBigStruct()
{
    return _bigStruct;
}

private ref BigStruct CreateRefBigStruct()
{
    return ref _bigStruct;
}
La imagen muestra los resultados de devolver una estructura por valor o por referencia, donde el hecho de devolverla por referencia mejora un 31% el tiempo usando .Net Core

Para poder conseguir esto, lo que tenemos que hacer es hacer es añadir el ‘ref’ a la firma de nuestro método. Tras esto, después del ‘return’ también añadimos ‘ref’, para indicar que el retorno es por valor. A la hora de consumir el retorno de este método utilizaremos variables ‘ref’ locales:

ref var item = ref CreateRefBigStruct();

Vale, pero con esto estamos devolviendo una referencia y podemos modificar el contenido de la estructura… ¿Esto se puede evitar de alguna manera? Pues la respuesta es sí, gracias a las herramientas del lenguaje disponibles podemos declarar el método como ‘readonly‘. De este modo conseguimos crear una referencia de solo lectura hacia la estructura, de modo que conseguimos lo mejor de los dos mundos.

private ref readonly BigStruct CreateRefBigStruct()
{
    return ref _bigStruct;
}
ref readonly var item = ref CreateRefBigStruct();

Span<T>/ReadOnlySpan<T>

La primera de estas herramientas es ‘ (opens in a new tab)» href=»https://docs.microsoft.com/es-es/dotnet/api/system.span-1?view=netcore-3.1″ target=»_blank»>Span<T>‘ y su versión de solo lectura ‘ (opens in a new tab)» href=»https://docs.microsoft.com/es-es/dotnet/api/system.readonlyspan-1?view=netcore-3.1″ target=»_blank»>ReadOnlySpan<T>‘. Gracias a ella vamos a generar una referencia hacia una colección en la memoria contigua que nos va a permitir hacer operaciones sobre ella. Todo esto sin tener que generar nuevas colecciones gracias a su método Slice. Esta memoria a la que apuntamos puede estar tanto en la pila como en el montón o incluso memoria no administrada. Imaginemos esto como una ventana móvil que apunta hacia una colección completa, cuando necesitemos acceder a una parte especifica de la colección, en vez de crear una subcolección, simplemente vamos a mover la ventana a la parte que nos interesa sin crear una colección nueva.

La imagen enseña mediante un diagrama que respecto a una colección completa, Span<T> permite abrir o cerrar la región que esta controlando mejorando así el rendimiento en .Net Core

‘Span<T>’ es un tipo de dato especial (ref struct) que nos garantiza que siempre se va a almacenar en la pila, por lo que solo podemos hacer uso de ella en situaciones que garanticen que esto se cumple. En caso contrario el compilador nos generará un error. Vamos a poder utilizar Span<T> como variable local, como parámetro o como retorno de un método, pero no como miembro de una clase o estructura.

Esto es porque, aunque las estructuras en principio van a la pila, no siempre tiene porque ser así. Si por ejemplo está dentro de una clase, la estructura se almacenará en el montón.

¿Y qué ventajas me da esto? Pues, por ejemplo, vamos a poder crear colecciones de datos en la pila mientras que si utilizásemos una lista o array, estarían en el montón. Para poder hacer esto vamos a utilizar la palabra reservada ‘stackalloc’ para indicar que esa memoria que queremos tiene que venir de la pila.

//Con reserva de memoria
int[] array = new int[1024];
//Sin reserva de memoria
Span<int> array = stackalloc int[1024];

Por poner un ejemplo, crear una colección de 100000 elementos y asignarles un valor:

public void InitializeWithArray()
{
    int[] array = new int[_lenght];
    for (var i = 0; i > _lenght; i++)
    {
        array[i] = i;
    }
}

public void InitializeWithStackalloc()
{
    Span<int> array = stackalloc int[_lenght];
    for (var i = 0; i > _lenght; i++)
    {
        array[i] = i;
    }
}

Nos aporta unos resultados muy claros:

La imagen muestra que utilizar Span consigue una reducción del tiempo de un 27% además de no consumir nada de memoria mientras que el método con array consume un total de 40 KB con lo que mejora el rendimiento en .Net Core

El hecho de utilizar Span<T> está suponiendo un ahorro del 27% en cuanto a ejecución y además estamos evitando el uso de 40 KB de memoria del montón.

La principal ventaja es que desde .Net Core 2.1 tiene un soporte nativo en el framework, por lo que ahora en .Net Core 3 son muchísimas las APIs del framework que nos da la opción de darle como parámetro o recibir como retorno objetos de tipo Span<T>, por lo que con unos cambios mínimos en nuestra manera de desarrollar podríamos aplicar una mejora en el rendimiento.

Memory<T>/ReadOnlyMemory<T>

Span<T> es muy útil como hemos visto a la hora de optimizar el uso de memoria y con ello el rendimiento en .Net Core, pero tiene sus limitaciones respecto a donde usarlo… Precisamente para suplir esas limitaciones está a nuestra disposición ‘ (opens in a new tab)» href=»https://docs.microsoft.com/es-es/dotnet/api/system.memory-1?view=netcore-3.1″ target=»_blank»>Memory‘ y su versión de solo lectura ‘ReadOnlyMemory‘. A diferencia de Span y ReadOnlySpan, estas dos nuevas versiones si pueden almacenarse en el montón y por tanto podemos usarlo en cualquier sitio.

ArrayPool<T>

Hasta ahora hemos planteado opciones para evitar el uso del montón para minimizar las recolecciones y evitar así tiempos muertos. Esto no siempre es posible porque la pila tiene un tamaño limitado. Si pretendemos almacenar grandes colecciones con estructuras en ella vamos a tener un desbordamiento…

Para evitar este problema podemos aplicar una estrategia de reciclaje de memoria gracias a ‘ (opens in a new tab)» href=»https://docs.microsoft.com/es-es/dotnet/api/system.buffers.arraypool-1?view=netcore-3.1″ target=»_blank»>ArrayPool‘. Cuando una aplicación arranca, automáticamente reserva una cierta cantidad de memoria en el montón que tiene a la espera para cuando se necesite. Precisamente gracias a ArrayPool vamos a pedir prestada esa memoria del montón para hacer las operaciones y después se la devolveremos. Empleando esta estrategia vamos a conseguir que no se necesiten hacer recolecciones sobre esa memoria y vamos a mejorar el rendimiento de aplicaciones .Net Core.

Para poder conseguir este objetivo, ArrayPool nos ofrece dos métodos con los que vamos a poder pedir y devolver memoria. Estos métodos son ‘Rent‘ y ‘Return‘.

var array = ArrayPool<BigStruct>.Shared.Rent(1024);
//Código que utiliza el array
ArrayPool<BigStruct>.Shared.Return(array);

Utilizando ArrayPool estamos pidiendo memoria al proceso y si no se la devolvemos, vamos a provocar una fuga de memoria que acabará en un fallo catastrófico.

Supongamos un caso donde tengamos una colección de 1000 posiciones de una estructura grande. La diferencia entre crear un array de la manera normal o utilizar ArrayPool es esta:

La imagen muestra que utilizar ArrayPool es más de 100 veces más eficiente además de no consumir nada de memoria

Como se puede comprobar, utilizar ArrayPool en torno a 100 veces más eficiente. Además de la mejora de la eficiencia, también hay un ahorro de la memoria utilizada por el proceso.

Conclusiones

Existen más métodos de optimización para mejorar el rendimiento en aplicaciones .Net Core con lo que conseguir mejoras muy importantes aunque en esta entrada he querido plantear los más generalistas. Dicho lo anterior vuelvo a reiterar que todo esto solo se debe utilizar si se necesita y solo si se necesita. El hecho de utilizar más structs y no pensar que las clases son la solución a todo es una buena idea, al igual que utilizar las APIs con Span siempre que se pueda, pero no volverse loco sin motivo. Optimizar código suele traer consigo el dificultar la lectura respecto a código sin optimizar. Por supuesto, antes de plantearse hacer cambios como estos, siempre hay que medir. La optimización prematura es una de las mayores fuentes de problemas a la hora de desarrollar.

¿Conocías estas maneras de mejorar el rendimiento para .Net Core? Si crees que me he dejado algún método importante no dudes en dejarlo en los comentarios y amplio esta entrada. Todas las herramientas disponibles en el cinturón siempre son buenas. 🙂

Para que cada uno pueda tomar sus propias conclusiones, he dejado el código en GitHub listo para poder ejecutarlo.

**La entrada Escribiendo código de alto rendimiento en .Net Core se publicó primero en Fixed Buffer.**

» Leer más, comentarios, etc...

Variable not found

Publicación self-contained y single-file en .NET Core

febrero 11, 2020 07:05

.NET CoreComo sabemos, para ejecutar cualquier tipo de aplicación .NET Core en un equipo necesitamos tener instalado el runtime o el SDK de la plataforma. Esto es algo que podemos hacer muy fácilmente, simplemente acudiendo a la página oficial de descargas, eligiendo nuestro sistema operativo y siguiendo las instrucciones de instalación.

El hecho de que en el equipo destino esté preinstalado el runtime es muy interesante, entre otras cosas porque permite asegurar de antemano que en él se encontrarán todas las dependencias (frameworks, bibliotecas, paquetes, metapaquetes) necesarios para una correcta ejecución. Por tanto, para distribuir nuestra aplicación sólo debemos generar lo relativo a nuestro código, el resto ya estará allí.
Esta forma de publicar aplicaciones se denomina framework-dependent, pues dependen de que determinados componentes del framework estén instalado en el destino.
Por ejemplo, el paquete de publicación de una aplicación de consola prácticamente vacía, que únicamente muestra el mensaje "Hello world!", ocuparía solo 175K:
D:\MyConsoleApp\output>dir

El volumen de la unidad D es Datos
El número de serie del volumen es: 8CBC-81E3

Directorio de D:\MyConsoleApp\output

09/02/2020 18:47 <DIR> .
09/02/2020 18:47 <DIR> ..
09/02/2020 18:46 428 MyConsoleApp.deps.json
09/02/2020 18:46 4.608 MyConsoleApp.dll
09/02/2020 18:46 169.984 MyConsoleApp.exe
09/02/2020 18:46 668 MyConsoleApp.pdb
09/02/2020 18:46 154 MyConsoleApp.runtimeconfig.json
5 archivos 175.842 bytes
2 dirs 463.058.874.368 bytes libres

D:\MyConsoleApp\output>_
Otra ventaja de este tipo de distribución es que es cross platform pura, es decir, podemos copiar los archivos a cualquiera de los sistemas operativos soportados y, siempre que dispongan del runtime, nuestra aplicación podrá correr sobre ellos sin problema.

Y todo esto está muy bien, pero, ¿qué pasa si quiero crear una aplicación portable, de forma que pueda distribuirla y ejecutarla sin necesidad de que el equipo destino tenga nada preinstalado?

Pues eso es lo que veremos en este post ;)

Publicación self-contained

Este tipo de publicación incluye todo lo necesario para que la aplicación funcione, sin necesidad de que el equipo destino disponga de componentes preinstalados. Es la opción más segura si desconocemos qué runtimes están instalados en el servidor, o incluso si queremos aislarnos de cambios o actualizaciones que pudieran instalarse en el servidor y que, de alguna forma, pudieran salpicarnos en el futuro.

Para publicar en modo self contained desde Visual Studio, sólo tendremos que acudir al perfil de publicación y editar sus settings de la siguiente forma:

Perfil de publicación estableciendo el deployment mode a self-contained y el target runtime a win-x64

Como se observa en la captura anterior, en el Deployment mode debemos seleccionar "Self-contained", mientras que en la opción Target Runtime tendremos que indicar el runtime sobre el que vamos a ejecutar la aplicación. Este paso es importante, pues hará que los binarios a incluir en el paquete distribuible sean los específicos para el entorno indicado.

Esto también podemos hacerlo si queremos generar el paquete de publicación desde la línea de comandos, por ejemplo como sigue:
D:\MyConsoleApp>dotnet publish --self-contained -r win-x64 -c release -o output

Microsoft (R) Build Engine versión 16.4.0+e901037fe para .NET Core
Copyright (C) Microsoft Corporation. Todos los derechos reservados.

Restauración realizada en 138,79 ms para D:\MyConsoleApp\MyConsoleApp.csproj.
MyConsoleApp -> D:\MyConsoleApp\bin\release\netcoreapp3.1\win-x64\MyConsoleApp.dll
MyConsoleApp -> D:\MyConsoleApp\output\

D:\MyConsoleApp\output>_
En cualquier caso, el principal inconveniente de este modelo de distribución es que los archivos a mover son bastantes más, por lo que los despliegues serán más lentos y ocuparán mucho más espacio en disco. Por ejemplo, si hacemos un dir en la carpeta de resultados de la publicación veremos que hemos pasado de tener cinco archivos a más de doscientos, y el tamaño de menos de 200Kb a 69Mb:
D:\MyConsoleApp\output>dir

El volumen de la unidad D es Datos
El número de serie del volumen es: 8CBC-81E3

Directorio de D:\MyConsoleApp\output

09/02/2020 18:56 <DIR> .
09/02/2020 18:56 <DIR> ..
20/04/2018 06:28 19.208 api-ms-win-core-console-l1-1-0.dll
20/04/2018 06:28 18.696 api-ms-win-core-datetime-l1-1-0.dll
20/04/2018 06:28 18.696 api-ms-win-core-debug-l1-1-0.dll
20/04/2018 06:28 18.696 api-ms-win-core-errorhandling-l1-1-0.dll
20/04/2018 06:29 22.280 api-ms-win-core-file-l1-1-0.dll
20/04/2018 06:37 18.696 api-ms-win-core-file-l1-2-0.dll
(... Omitidos más de 200 archivos ...)
09/12/2019 03:39 14.928 System.Xml.XmlDocument.dll
09/12/2019 03:40 16.768 System.Xml.XmlSerializer.dll
09/12/2019 03:40 14.200 System.Xml.XPath.dll
09/12/2019 03:39 15.952 System.Xml.XPath.XDocument.dll
20/04/2018 06:37 1.016.584 ucrtbase.dll
09/12/2019 03:40 15.440 WindowsBase.dll
225 archivos 69.122.606 bytes
2 dirs 462.918.475.776 bytes libres

D:\MyConsoleApp\output>_
Sí, pensaréis que es un exceso para mostrar un simple "Hola mundo", pero si tenemos en cuenta que estos archivos incluyen el framework y todas sus bibliotecas no sale tan mal parada la cosa. Lo importante en este caso es que podemos copiar esta carpeta a cualquier servidor Windows x64 y funcionará correctamente, sin necesidad de tener nada preinstalado.

Pero aún podemos mejorarlo un poco...

Eliminando peso: Trimming de paquetes

Cuando hemos echado un ojo al contenido de la carpeta de destino de la publicación, vimos archivos como System.Xml.XmlSerializer.dll o System.Xml.XPath.dll... ¿realmente necesitamos desplegar estos ensamblados en nuestra aplicación, que sólo muestra un "Hello world!"? Seguro que no.

Desde .NET Core 3.0, el SDK incluye de serie una funcionalidad que permite eliminar de los archivos distribuibles los paquetes que no sean utilizados por nuestra aplicación o sus dependencias. Para activarlo, basta con añadir el elemento <PublishTrimmed> en el archivo .csproj del proyecto:
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
Hecho esto, si volvemos a repetir la operación de publicación, veremos que el paquete de publicación se reducirá bastante:
D:\MyConsoleApp>dotnet publish --self-contained -r win-x64 -c release -o output

Microsoft (R) Build Engine versión 16.4.0+e901037fe para .NET Core
Copyright (C) Microsoft Corporation. Todos los derechos reservados.

Restauración realizada en 26,72 ms para D:\MyConsoleApp\MyConsoleApp.csproj.
MyConsoleApp -> D:\MyConsoleApp\bin\Release\netcoreapp3.1\win-x64\MyConsoleApp.dll
Se está optimizando el tamaño de los ensamblados, lo que puede cambiar el
comportamiento de la aplicación. Asegúrese de probarlo después de publicar.
Consulte https://aka.ms/dotnet-illink
MyConsoleApp -> D:\MyConsoleApp\output\

D:\MyConsoleApp>dir output

El volumen de la unidad D es Datos
El número de serie del volumen es: 8CBC-81E3

Directorio de D:\MyConsoleApp\output
09/02/2020 19:05 <DIR> .
09/02/2020 19:05 <DIR> ..
20/04/2018 06:28 19.208 api-ms-win-core-console-l1-1-0.dll
20/04/2018 06:28 18.696 api-ms-win-core-datetime-l1-1-0.dll
20/04/2018 06:28 18.696 api-ms-win-core-debug-l1-1-0.dll
20/04/2018 06:28 18.696 api-ms-win-core-errorhandling-l1-1-0.dll
(... Omitidos más de 50 archivos ...)
09/02/2020 19:05 62.464 System.Console.dll
07/12/2019 16:40 9.555.840 System.Private.CoreLib.dll
09/02/2020 19:05 74.752 System.Runtime.Extensions.dll
20/04/2018 06:37 1.016.584 ucrtbase.dll
63 archivos 26.499.046 bytes
2 dirs 462.951.739.392 bytes libres

D:\MyConsoleApp>_
Mucho mejor ahora: hemos pasado de 225 archivos a 63, y reducido el peso de 69 a 26Mb. Aunque sigue siendo demasiado para un simple "Hello world!", al menos sabemos que es el mínimo al que podemos aspirar si queremos que el paquete distribuible de nuestra aplicación incluya el framework sobre el que será ejecutada.

Un último detalle: como hemos comentado anteriormente, el trimming eliminará los paquetes que detecte que no son utilizados, pero puede haber ocasiones en las que nos interesa que este mecanismo no elimine algún ensamblado en particular. Por ejemplo, si nuestra aplicación utiliza reflexión o cualquier otro mecanismo para cargar o utilizar ensamblados, el trimmer no los detectará y asumirá que no se están usando, lo que provocará errores en tiempo de ejecución.

Para indicar que un ensamblado debe ser incluido obligatoriamente en el paquete de publicación, podemos usar el elemento <TrimmerRootAssembly> en el .csproj:
<ItemGroup>
<TrimmerRootAssembly Include="System.Xml.XmlSerializer.dll" />
</ItemGroup>

Publicación single-file: ¡un único ejecutable!

A partir de .NET Core 3, tenemos disponible un nuevo modelo de distribución que permite incluir en un único ejecutable todo lo necesario para que nuestra aplicación funcione sin tener nada preinstalado.

Podemos publicar como archivo único mediante una orden de la CLI como la siguiente:
D:\MyConsoleApp>dotnet publish -r win10-x64 -p:PublishSingleFile=true -o output

Microsoft (R) Build Engine versión 16.4.0+e901037fe para .NET Core
Copyright (C) Microsoft Corporation. Todos los derechos reservados.

Restauración realizada en 124,52 ms para D:\MyConsoleApp\MyConsoleApp.csproj.
MyConsoleApp -> D:\MyConsoleApp\bin\Debug\netcoreapp3.1\win10-x64\MyConsoleApp.dll
Se está optimizando el tamaño de los ensamblados, lo que puede cambiar el
comportamiento de la aplicación. Asegúrese de probarlo después de publicar.
Consulte https://aka.ms/dotnet-illink
MyConsoleApp -> D:\MyConsoleApp\output\

D:\MyConsoleApp>dir output

El volumen de la unidad D es Datos
El número de serie del volumen es: 8CBC-81E3

Directorio de D:\MyConsoleApp\output

09/02/2020 19:10 <DIR> .
09/02/2020 19:10 <DIR> ..
09/02/2020 19:10 26.501.267 MyConsoleApp.exe
09/02/2020 19:10 680 MyConsoleApp.pdb
2 archivos 26.501.947 bytes
2 dirs 462.872.199.168 bytes libres

D:\MyConsoleApp>_
En lugar de tener que indicar tantos parámetros cada vez, también podríamos conseguirlo modificando el archivo .csproj. Para ello, bastaría con establecer a true el elemento <PublishSingleFile>, aunque al hacerlo, además, será obligatorio introducir el runtime de destino en el item <RuntimeIdentifier>. El resultado final podría ser algo así (trimming incluido):
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<PublishSingleFile>true</PublishSingleFile>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
En cualquier caso, fijaos que podríamos distribuir la aplicación, el runtime y el framework en un único ejecutable de 26Mb (usando el trimmer).

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 390

febrero 10, 2020 07:27

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

    Web / HTML / CSS / Javascript

    Visual Studio / Complementos / Herramientas

    Xamarin / Mobile

    Otros

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Navegapolis

    No copie los modelos ágiles de los expertos o de otras empresas

    febrero 04, 2020 04:11

    No copie modelos
    • No copie los métodos y prácticas de otras empresas, ni implemente un modelo diseñado por expertos. Estudie y aprenda de ellos, pero experimente y adáptese a lo que funciona para usted y su cultura.
    • No contrate a una gran empresa de consultoría para transformar su organización de manera expédita o para implementar nuevas metodologías o prácticas... sus equipos no desarrollarán la confianza o la capacidad de sostener, continuar mejorando o adaptar y desarrollar nuevos procesos y comportamientos por su cuenta.
    • Desarrolle sus propios coaches. Inicialmente puede que necesite contratar a un coach externo para establecer una base sólida, pero en última instancia el agente del cambio debe ser propio. La intensidad del coahing es clave para el mantenimiento y el crecimiento.


    A estas conclusiones llegan Nicole Forsgren, Jez Humble y Gene Kim en 2017 tras analizar durante 4 años el trabajo en 2.000 organizaciones TIC, comprendiendo desde pequeñas startups de menos de 5 empleados, hasta grandes compañías de 10.000 y cuyos resultados publican en su libro Accelerate.

     

    » Leer más, comentarios, etc...

    Navegapolis

    Adapte los modelos ágiles a lo que funciona para usted y su cultura.

    febrero 04, 2020 04:11

    No copie modelos
    • No copie los métodos y prácticas de otras empresas, ni implemente un modelo diseñado por expertos. Estudie y aprenda de ellos, pero experimente y adáptese a lo que funciona para usted y su cultura.
    • No contrate a una gran empresa de consultoría para transformar su organización de manera expédita o para implementar nuevas metodologías o prácticas... sus equipos no desarrollarán la confianza o la capacidad de sostener, continuar mejorando o adaptar y desarrollar nuevos procesos y comportamientos por su cuenta.
    • Desarrolle sus propios coaches. Inicialmente puede que necesite contratar a un coach externo para establecer una base sólida, pero en última instancia el agente del cambio debe ser propio. La intensidad del coahing es clave para el mantenimiento y el crecimiento.


    A estas conclusiones llegan Nicole Forsgren, Jez Humble y Gene Kim en 2017 tras analizar durante 4 años el trabajo en 2.000 organizaciones TIC, comprendiendo desde pequeñas startups de menos de 5 empleados, hasta grandes compañías de 10.000 y cuyos resultados publican en su libro Accelerate.

     

    » Leer más, comentarios, etc...

    Picando Código

    [Libro] The Rise and Fall of the Dinosaurs por Steve Brusatte

    febrero 04, 2020 02:30

    Después de visitar la exhibición de Tiranosaurios en el Museo Nacional de Escocia, pasé por la tienda de regalos. Con cada exhibición, la tienda vende artículos relacionados: juguetes, camisetas, libros para nínos y también libros científicos sobre el tema en particular. Entre estos libros me encontré con el de Steve Brusatte, paleontólogo cuyo trabajo podemos encontrar en la exhibición mencionada. The Rise and Fall of the Dinosaurs (El Auge y Caída de los Dinosaurios, ¡también disponible en español!) es un libro que recorre toda la historia de los dinosaurios. Desde el período Paleozoico (y un poco antes) donde gran parte de la tierra se concentraba en el supercontinente de Pangea, hasta su extinción y cómo fueron evolucionando a lo largo de sus millones de años de dominio sobre el planeta.

    Es un libro extremadamente entretenido, está basado en descubrimientos científicos y datos, pero todo está contado desde la experiencia del autor a través de historias. Así que no es totalmente académico, lo cual podría resultar aburrido, a pesar de estar basado en conocimiento científico.

    The Rise and Fall of the Dinosaurs - Steve Brusatte

     

    ¡Hay mucho para aprender! Por más que sepamos sobre dinosaurios y otros animales prehistóricos la paleontología está en constante evolución. Particularmente estos días se encuentra -en promedio- una especie nueva de dinosaurio por semana (dato que aprendí en el libro). Así que es una buena forma de refrescar conocimientos y estar al tanto de las última teorías sobre la evolución en la tierra.

    Entre las anécdotas e historias interesantes, el autor habla sobre cómo se descubrió el Herrerasaurio en Argentina. Fue en el Valle de la Luna en la provincia de San Juan. Tengo grabado ese lugar en la memoria de haber leído sobre fósiles en alguna de las revistas científicas que leía cuando era chico (Conozca Más, Descubrir, Muy Interesante). Pero habiendo sido “fanático de los dinosaurios” desde chico, fue un nombre familiar que me hizo conectar lo nuevo que estaba leyendo con algo muy específico del pasado. También cuenta la historia sobre una expedición a la Isla de Skye en Escocia que me resultó particularmente entretenida. Voy a tener que ir, visitar el museo del que habla y salir a explorar para buscar huellas o fósiles de dinosaurios.

    En el libro vamos a conocer más sobre los dinosaurios, pero también aprender cómo el período Pérmico (que vino antes de los dinosaurios y terminó con una extinción muchísimo más masiva que la que mató a los dinosaurios) fue igualmente fascinante, así como lo fueron los dinosauriomorfos y otras especies. Cuando se adentra en el tema, reaviva la fascinación por los dinosaurios. ¿Por qué/cómo los Saurópodos se volvieron tan grandes? ¿Cómo podemos imaginarlos o concebir animales de tal tamaño? Los capítulos sobre los Tiranosaurios, el T-Rex y toda su familia, son fascinantes y alimentan todavía más la admiración por la especie y dejan ver el amor incondicional que varios estudiosos le tienen a ésta especie. Es esa magia que tienen los dinosaurios, fueron animales reales, con características muy distintas y muy similares a los animales de hoy, pero sólo podemos teorizar e imaginarlos (por ahora).

    En varias oportunidades paraba de leer para ir a mi libro de referencia o a internet para aprender más sobre una especie en particular. Mi libro de referencia actual es una Enciclopedia Ilustrada por Dougal Dixon (autor escocés), con 355 especies. Pero creo que no está muy actualizado… (¡los Tiranosaurios en el libro no tienen plumas!). Una de las cosas que disfruto de leer libros científicos es que me hace pensar en cosas que de repente no me hubiera cuestionado antes. Fue así que en un momento empecé a plantearme, ¿cómo carajos en algún punto de la historia un animal de repente empezó a volar? ¿Cómo se llegó hasta ahí? Específicamente qué cambió para que un animal terminara recorriendo los cielos. Potencialmente el autor haya plantado la semilla de esa idea a propósito, porque el capítulo 8 “Los dinosaurios toman vuelo” encontramos respuestas a esas preguntas.

    Siguiendo con el tema de vuelo, desde que se introdujo masivamente la teoría de que los pájaros habían evolucionado de los dinosaurios tengo una nueva apreciación por éstos animales. Pero no sólo evolucionaron de los dinosaurios, son dinosaurios modernos, evolución de aquellos que sobrevivieron la extinción. Y es otra cosa interesante que reafirma el libro, los tenemos acá revoloteando entre nosotros.

    Steve Brusatte se para en los hombros de gigantes, algo intrínseco en el mundo científico. Pero el autor lo demuestra en cada oportunidad que tiene. Referencia y reconoce a colegas y amigos del rubro a lo largo de todo el libro. Incluso me quedé con uno o dos títulos que menciona para buscar y leer más adelante.

    El Auge y Caída de los Dinosaurios es un excelente libro que nos enseña lo último en materia de dinosaurios en un mundo post Jurassic Park. Lo recomiento ampliamente si alguna vez le gustaron o todavía le gustan los dinosaurios. Si disfrutaron cualquiera de las películas de Jurassic Park, probablemente disfruten este libro. Léanlo aunque sea por corregir las inexactitudes científicas que nos hicieron creer las películas, como que un T-Rex no nos vería si nos quedamos quietos. Si les interesa la ciencia, los animales, y la historia de nuestro planeta, probablemente también disfruten este libro.

    Steve Brusatte nació en Illinois, Estados Unidos pero vive en Escocia dando clases en la Universidad de Edimburgo. Pueden seguirlo en Twitter en @SteveBrusatte. Les recomiendo buscar el libro en su biblioteca o librería favorita, y como comentaba antes, está disponible también en español.

    » Leer más, comentarios, etc...

    Picando Código

    ATuServicio 2020

    febrero 01, 2020 03:41

    Como casi todos los 1° de febrero desde 2015, este año se lanza una versión actualizada de ATuServicio. Y como todos los meses de enero de los últimos años, con DATA anduvimos a las corridas arreglando, actualizando y corrigiendo cosas para poder tener todo en marcha. Se van a venir algunas cosas nuevas en breve, pero mientras tanto ya se pueden ver los datos actualizados ahora que se volvió a abrir el corralito mutual en Uruguay.

    ATuServicio.uy

     

    Copio y pego el mensaje de DATA:

    Empieza un nuevo período de movilidad regulada (MORE), también conocido como “corralito”, y con él vuelve A Tu Servicio, como desde hace seis años.

    Desde hoy, sábado 1º, y hasta el 28 de febrero, los/as usuarios/as del sistema de salud que cumplan con los requisitos, podrán cambiar de prestador de salud si así lo desean. Las personas interesadas en hacerlo deberán presentarse con su cédula de identidad en la mutualista a la cual quieren ingresar.

    Decisiones informadas y basadas en datos

    A Tu Servicio es una herramienta que permite obtener, visualizar y comparar datos de los diferentes prestadores de salud de Uruguay, para promover la toma de decisiones informadas de los/as usuarios/as y la transparencia del sistema.

    Por eso, si estás pensando en cambiar de prestador, te invitamos a ingresar a la herramienta para comprar datos y así definir qué es lo que más te conviene, según las prioridades que vos definas. Y si no, igual es una gran oportunidad para conocer más sobre tu prestador de salud 😁.

    Esta plataforma es desarrollada por DATA y el Ministerio de Salud Pública.

    Más información:

    » Leer más, comentarios, etc...

    Picando Código

    Exhibición sobre Tiranosaurios en el Museo Nacional de Escocia

    enero 29, 2020 02:30

    Del 23 de enero al 4 de mayo el Museo Nacional de Escocia en Edimburgo está presentando una exhibición sobre Tiranosaurios 🦖

    Mientras que la especie más famosa es el poderoso T. rex, los tiranosaurios venían en todas formas y tamaños, y su historia se extiende por más de 100 millones de años. La exhibición muestra especímenes fósiles extremadamente raros, esqueletos de yeso – incluyendo uno de “Scotty”, uno de los esqueletos de T. rex más grandes y completos del mundo – e increíbles modelos de dinosaurios con plumas. Los visitantes también podrán explorar la diversidad de cráneos de tiranosaurio y encontrar lo que variaciones en la estructura nos pueden decir sobre distintas estrategias de caza y alimentación.

    Más información.

    El domingo estuve por el museo y disfruté por algunas horas un tiempo con estos fósiles y aprendiendo más sobre la familia Tiranosauria. Por suerte he tenido la oportunidad de ver más de un modelo de esqueleto de Tiranosaurio. El último había sido recientemente Tristan Otto en Berlín. El de Scotty es bastante impresionante, y está bueno ver su tamaño en comparación a otros especímenes de la familia. Me encanta presenciar esos modelos e imaginar lo que sería estar frente a un animal de ese tipo.

    Las placas informativas de la exhibición incluian información de lo más actualizado que se conoce sobre la familia de Tiranosaurios. También hay menúes interactivos y demás para los más pequeños (y no tan pequeños). Hay también unos modelos de tiranosaurio con plumas como dice la descripción. Esto es algo que no se sabía hasta hace unos años y ahora se cree que todos o la mayoría de los tiranosaurios eran emplumados.

    Algunas fotos de la exhibición:

    Cráneos de Tiranosaurio

    Cráneos de Tiranosaurio

    Tiranosaurios varios

    Tiranosaurios varios

    Más Tiranosaurios

    Más Tiranosaurios

    Modelo de Scotty el Tiranosaurio

    Modelo de Scotty el Tiranosaurio

    Modelo de Scotty el Tiranosaurio

    Modelo de Scotty el Tiranosaurio

    El Museo Nacional de Escocia tiene entrada gratuita (aunque se sugiere dejar alguna donación), y es espectacular. Así que cuando anden por Edimburgo, es un paseo súper recomendable. 

    Cuentan con su propio modelo permanente de Tiranosaurio Rex basado en MOR 555, también conocido como “Wankel Rex”. Con un 46% del esqueleto encontrado, es el segundo esqueleto más grande y más completo de T. Rex (más información en el sitio del museo). También hay un modelo de Estegosaurio y un montón de cosas interesantes más. Ya que estaba en el museo pasé a saludar a Wankel para que no se pusiera celoso del resto de la exhibición…

    MOR 555 o "Wankel Rex"

    MOR 555 o “Wankel Rex”

    » Leer más, comentarios, etc...

    Meta-Info

    ¿Que es?

    Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

    rss subscripción

    Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
    planetacodigo

    planetacodigo

    Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

    Idea: Juanjo Navarro

    Diseño: Albin