Java: WallClock versus System.currentTimeMillis

Como de costoso es llamar a System.currentTimeMillis()?

Si tenemos una función dependiente del tiempo y a esa funcion la llamamos, digamos… 500.000.000 veces y tarda 9911ms; hay alguna manera de hacer que sea más rápido? Bueno, si eludimos la llamada al reloj del sistema mediante un pequeño cache puede que ganemos unos segundos…

Esto sería el esqueleto del sistema, un thread que actualiza el reloj a intervalos regulares y una funcion q devuelve el último valor conocido:

public class WallClock implements Runnable {
  private static final int clockLatency = 1;  // WallClock time refresh (millis)
  private static WallClock self = null;       // Singleton
  private volatile long lastTimeMillis = 0L;  // Last time cached from SystemClock
  private volatile Thread clocker = null;     // WallClock Thread

  public static synchronized WallClock getInstance() {
    if (self == null) {
      self = new WallClock();
      self.init();
    }
    return self;
  }

  public long currentTimeMillis() {
    return lastTimeMillis;
  }

  private void init() {
    if (clocker == null) {
      clocker = new Thread(this);
      clocker.setDaemon(true);
      clocker.start();
    }
  }
  public void destroy() {
    clocker.interrupt();
  }

  /**
   * Run, baby, run
   */
  public void run() {
    try {
      while (!Thread.currentThread().isInterrupted()) {
        lastTimeMillis = System.currentTimeMillis();
        Thread.sleep(clockLatency);
      }
    } catch (InterruptedException ie) {
      /* Allow thread to exit */
    } finally {
      clocker = null;
    }
  }
}

Y cuanto tiempo ganamos con esta aproximación?

  public static void main(final String[] args) throws Throwable {
    final int TOTAL = 50000000;
    long ax = 0, ts = 0;
    //
    ts = System.currentTimeMillis();
    for (int i = 0; i < TOTAL; i++) {
      ax += (System.currentTimeMillis() & 0xF);
    }
    System.out.println("SystemClock: " + (System.currentTimeMillis() - ts) + "ms");
    //
    final WallClock wc = WallClock.getInstance();
    ts = System.currentTimeMillis();
    for (int i = 0; i < TOTAL; i++) {
      ax += (wc.currentTimeMillis() & 0xF);
    }
    System.out.println("WallClock: " + (System.currentTimeMillis() - ts) + "ms");
    if ((ax > 0) || (ts > 0)) // Dummy (always true)
      wc.destroy();
  }
SystemClock: 9911ms
WallClock..: 1820ms

El resultado es un aumento de la velocidad x5, el precio q se paga es tener un thread en background y algo de perdida de precision en los valores del reloj, pero es lo que tienen los caches.

Source code: WallClock.java

Java: The Thread Unsafe SimpleDateFormat

A muchos nos ha pasado que, quizá por la tímida nota del JavaDoc a pie de pagina del SimpleDateFormat (Date formats are not synchronized), hemos visto a la clase morder el polvo, aqui el ejemplo:

Leer más de esta entrada

MySQL: Small Guide for Simple Partitioning

Una mini guia de particionamiento sencillo en MySQL 5.5 basado en fecha:

Nota(1): Hay que incluir en la PK el campo que quieras usar en el particionado, así te ahorraras el error:

ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function
CREATE TABLE IF NOT EXISTS T1 (
  ID INT NOT NULL AUTO_INCREMENT,
  PART DATE NOT NULL,
  NUM INT NOT NULL,
  PRIMARY KEY (ID,PART))
ENGINE = InnoDB 
PARTITION BY RANGE COLUMNS(PART) ( 
  PARTITION p20140417 VALUES LESS THAN ('2014-04-18'),  
  PARTITION p20140418 VALUES LESS THAN ('2014-04-19'),  
  PARTITION p20140419 VALUES LESS THAN ('2014-04-20')
);

Nota(2): Ojo con las columnas DATE/DATETIME, se almacena la fecha tal cual (a diferencia de un TIMESTAMP que se almacena en formato UTC).

Para ver los particionamientos que hay definidos:

SELECT TABLE_NAME,PARTITION_NAME,TABLE_ROWS 
  FROM INFORMATION_SCHEMA.PARTITIONS 
 WHERE PARTITION_NAME IS NOT NULL;
+------------+----------------+------------+
| TABLE_NAME | PARTITION_NAME | TABLE_ROWS |
+------------+----------------+------------+
| T1         | p20140417      |          3 |
| T1         | p20140418      |          3 |
| T1         | p20140419      |          2 |
+------------+----------------+------------+
3 rows in set (0.01 sec)

Para poder sacar los planes de acceso del particionamiento:

EXPLAIN PARTITIONS 
 SELECT * FROM T1
  WHERE ID=1 
    AND PART = UTC_DATE(); 
+----+-------------+-------+------------+-------+---------------+---------+---------+-------------+------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref         | rows | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------------+------+-------+
|  1 | SIMPLE      | T1    | p20140417  | const | PRIMARY       | PRIMARY | 11      | const,const |    1 |       |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------------+------+-------+
1 row in set (0.00 sec)
EXPLAIN PARTITIONS 
 SELECT * FROM T1
  WHERE ID=1;
+----+-------------+-------+-------------------------------+------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | partitions                    | type | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+-------+-------------------------------+------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | T1    | p20140417,p20140418,p20140419 | ref  | PRIMARY       | PRIMARY | 8       | const |    2 |       |
+----+-------------+-------+-------------------------------+------+---------------+---------+---------+-------+------+-------+
1 row in set (0.00 sec)

Para añadir un nuevo particionado:

ALTER TABLE T1
ADD PARTITION (
  PARTITION p20140420 VALUES LESS THAN ('2014-04-21')
);

Para hacer el DROP de una particion y los datos contenidos en ella:

ALTER TABLE T1
DROP PARTITION p20140417;

Y la select de prueba que usa la columna de particionamiento (PART):

-- asumiento que hoy es 2014-04-17 y tenemos 365 particionamientos (1 por dia)
SELECT * FROM T1
 WHERE ID=3 
   AND PART BETWEEN UTC_DATE()-1 AND UTC_DATE()+1;
-- en este caso la busqueda estaria entre: p20140416, p20140417 y p20140418

Nota(3): El numero maximo de particiones por tabla es de 1024.

Referencias:
Restrictions and Limitations on Partitioning
Partitioning Types
RANGE Partitioning
RANGE COLUMNS partitioning
ALTER TABLE
ALTER TABLE Partition Operations
Data Type Storage Requirements
The DATE, DATETIME, and TIMESTAMP Types
Date and Time Functions

Java: HttpURLConnection and ignored setRequestProperty headers

Si usas HttpURLConnection es posible que haya ciertas cabeceras que no puedas usar mediante setRequestProperty (esto no siempre fué así, fue un cambio q añadieron en Java 1.6.0_22, sin aviso ni documentación oficial que yo haya encontrado), quizá por una cuestion de seguridad en Applets/Navegadores Java?.

Aquí está el origen del problema:

sun.net.www.protocol.http.HttpURLConnection
/*
 * Restrict setting of request headers through the public api
 * consistent with JavaScript XMLHttpRequest2 with a few
 * exceptions. Disallowed headers are silently ignored for
 * backwards compatibility reasons rather than throwing a
 * SecurityException. For example, some applets set the
 * Host header since old JREs did not implement HTTP 1.1.
 * Additionally, any header starting with Sec- is
 * disallowed.
 *
 * The following headers are allowed for historical reasons:
 *
 * Accept-Charset, Accept-Encoding, Cookie, Cookie2, Date,
 * Referer, TE, User-Agent, headers beginning with Proxy-.
 *
 * The following headers are allowed in a limited form:
 *
 * Connection: close
 *
 * See http://www.w3.org/TR/XMLHttpRequest2.
 */
private static final boolean allowRestrictedHeaders;
private static final String[] restrictedHeaders = {
    /* Restricted by XMLHttpRequest2 */
    //"Accept-Charset",
    //"Accept-Encoding",
    "Access-Control-Request-Headers",
    "Access-Control-Request-Method",
    "Connection", /* close is allowed */
    "Content-Length",
    //"Cookie",
    //"Cookie2",
    "Content-Transfer-Encoding",
    //"Date",
    //"Expect",
    "Host",
    "Keep-Alive",
    "Origin",
    // "Referer",
    // "TE",
    "Trailer",
    "Transfer-Encoding",
    "Upgrade",
    //"User-Agent",
    "Via"
};
// ... cut ...

static {
  // ... cut ...
  allowRestrictedHeaders = ((Boolean)java.security.AccessController.doPrivileged(
    new sun.security.action.GetBooleanAction(
      "sun.net.http.allowRestrictedHeaders"))).booleanValue();
  // ... cut ...
}

Así, por ejemplo, si quieres hacer una peticion HTTP PUT/POST con el body comprimido usando un HttpURLConnection + GZIPOutputStream con su correspondiente setRequestProperty(“Content-Transfer-Encoding”, “gzip”); cuando llegue al servidor no será capaz de entender los datos.

El workarround, definir la System Property:

-Dsun.net.http.allowRestrictedHeaders=true

Habría sido buena idea añadir un comentario en el metodo “setRequestProperty”, verdad?

Referencias:
Java BUG#6996110: Won’t Fix
Source of HttpURLConnection

Java: Country names and iso3166 for i18n

Para sacar la lista de Paises en un Idioma concreto con su codigo ISO:

import java.util.Locale;

public class Countries {
  public static void main(final String[] args) {
    // ENGLISH, SPANISH, FRENCH, ITALIAN, GERMAN
    final Locale lang = Locale.ENGLISH;
    final String[] countries = Locale.getISOCountries();
    for (final String countryCode : countries) {
      final Locale locale = new Locale("", countryCode);
      System.out.println(locale.getCountry() + "=" + 
                         locale.getDisplayCountry(lang));
    }
  }
}

Esto devuelve el codigo de pais en formato ISO-3166-1 (2 letras) y su texto humano, tal que así:

ES=Spain
FR=France
...etc...

Para recuperar el Texto de un pais partiendo del codigo ISO (2 letras) sería:

public static String getCountry(final String isoCode, final Locale lang) {
  return new Locale("", isoCode).getDisplayCountry(lang);
}
getCountry("ES", Locale.GERMAN) => "Spanien"

Referencia: Locale

Linux: Performance Analysis and Tools

Java: Speed Limit on InputStream/OutputStream

Si necesitas reducir la velocidad de un InputStream/OutputStream de un modo sencillo, esta es la solución!

public class ThrottleStream {
	protected final int speedLimitBytesSecond;
	protected int currentTransferredBytes = 0;
	protected long lastTimeStampSeconds = 0;

	protected ThrottleStream(final int maxSpeedInKilobytesPerSecond) {
		this.speedLimitBytesSecond = maxSpeedInKilobytesPerSecond * 1024;
	}

	protected void addAndWait(final int transferred) throws IOException {
		long now = System.currentTimeMillis() / 1000;
		if (now != lastTimeStampSeconds) {
			currentTransferredBytes = 0;
			lastTimeStampSeconds = now;
		}
		currentTransferredBytes += transferred;
		if (currentTransferredBytes >= speedLimitBytesSecond) {
			try {
				while (true) {
					Thread.sleep(10);
					now = System.currentTimeMillis() / 1000;
					if (now != lastTimeStampSeconds) {
						currentTransferredBytes = 0;
						lastTimeStampSeconds = now;
						break;
					}
				}
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
				throw new IOException(e);
			}
		}
	}

	/**
	 * Speed Limit de un InputStream
	 */
	public static class ThrottleInputStream extends FilterInputStream {
		final ThrottleStream limit;

		public ThrottleInputStream(final InputStream in, 
				final int maxSpeedInKilobytesPerSecond) {
			super(in);
			limit = new ThrottleStream(maxSpeedInKilobytesPerSecond);
		}

		@Override
		public int read() throws IOException {
			final int read = super.read();
			limit.addAndWait(read);
			return read;
		}

		@Override
		public int read(final byte[] b) throws IOException {
			final int read = super.read(b);
			limit.addAndWait(read);
			return read;
		}

		@Override
		public int read(final byte[] b, final int off, final int len) 
				throws IOException {
			final int read = super.read(b, off, len);
			limit.addAndWait(read);
			return read;
		}
	}

	/**
	 * Speed Limit de un OutputStream
	 */
	public static class ThrottleOutputStream extends FilterOutputStream {
		final ThrottleStream limit;

		public ThrottleOutputStream(final OutputStream out, 
				final int maxSpeedInKilobytesPerSecond) {
			super(out);
			limit = new ThrottleStream(maxSpeedInKilobytesPerSecond);
		}

		@Override
		public void write(final int b) throws IOException {
			super.write(b);
			limit.addAndWait(1);
		}

		@Override
		public void write(final byte[] b) throws IOException {
			super.write(b);
			limit.addAndWait(b.length);
		}

		@Override
		public void write(final byte[] b, final int off, final int len) 
				throws IOException {
			super.write(b, off, len);
			limit.addAndWait(len);
		}
	}
}

Source code: ThrottleStream.java

Linux: Broken sar on vm clone

Si clonas una maquina en caliente lo más posible es que el “sar” (sysstat) deje de funcionar cuando le pides el historico:

# sar
Requested activities not available in file /var/log/sa/sa12

Para resetearlo (Open-SuSE):

# rm -f /var/log/sa/sa*; /etc/init.d/boot.sysstat start
Running sadc [done]

# sar
Linux x.x.xx.xx-x.x-pae (box1)  02/12/14        _i686_

01:39:29 PM       LINUX RESTART

01:45:02 PM     CPU     %user     %nice   %system   %iowait    %steal     %idle
01:55:01 PM     all      4.59      0.00      0.40      0.00      0.00     95.01
02:05:01 PM     all      7.11      0.00      0.67      0.00      0.00     92.22
02:15:01 PM     all     11.34      0.00      1.12      0.00      0.00     87.53
02:25:01 PM     all     14.48      0.00      1.44      0.00      0.00     84.08
02:35:01 PM     all      8.17      0.00      0.80      0.00      0.00     91.03
02:45:01 PM     all      4.52      0.00      0.41      0.00      0.00     95.06
02:55:01 PM     all      3.31      0.00      0.32      0.00      0.00     96.37
03:05:01 PM     all     24.12      0.00      2.98      0.00      0.00     72.89
03:15:01 PM     all      6.82      0.00      0.62      0.00      0.00     92.56
03:25:01 PM     all      5.43      0.00      0.48      0.00      0.00     94.09
Average:        all      8.98      0.00      0.92      0.00      0.00     90.10

Linux: Setting Hostname from DHCP-Server in Ubuntu

Si quieres que tu Linux coja el nombre de Host desde DHCP (probado en Ubuntu 12.04 -Precise-)

#!/bin/sh
# Filename: /etc/dhcp/dhclient-exit-hooks.d/hostname
# Purpose:  Set the hostname of the system provided by DHCP (option 12).
#
case "$reason" in
  BOUND|RENEW|REBIND|REBOOT)
    if [ "$new_host_name" != "" ]; then
      echo $new_host_name > /etc/hostname;
      hostname $new_host_name;
    fi
    echo dhclient-exit-hooks.d/hostname: Dynamic Hostname = $new_host_name;
  ;;
esac

El script aparentemente tiene acceso a estas variables de entorno (la documentacion no las explica todas):

interface=eth0
reason=RENEW
# ---
new_expiry=1391268994
new_dhcp_lease_time=300
new_dhcp_message=ACME
new_dhcp_message_type=5
new_dhcp_server_identifier=192.168.1.1
new_network_number=192.168.1.0
new_subnet_mask=255.255.255.0
new_broadcast_address=192.168.1.255
new_ip_address=192.168.1.101
new_routers=192.168.1.1
new_host_name=test1
new_domain_name=acme.com
new_domain_name_servers=192.168.1.1 192.168.1.2
# ---
old_expiry=1391268724
old_dhcp_lease_time=300
old_dhcp_message=ACME
old_dhcp_message_type=5
old_dhcp_server_identifier=192.168.1.1
old_network_number=192.168.1.0
old_subnet_mask=255.255.255.0
old_broadcast_address=192.168.1.255
old_ip_address=192.168.1.101
old_routers=192.168.1.1
old_host_name=test1
old_domain_name=acme.com
old_domain_name_servers=192.168.1.1 192.168.1.2

Referencias:
man 8 dhclient-script
man 5 dhclient.conf

Java: Universal Placeholder for InputStreams

Los ficheros XML tienen una sintaxis muy completa y los ficheros de Properties un formato sencillo de manipular:

# placeholders.properties

user = root
password = secret

Y como queremos ambas cosas, los “placeholders” nos permiten hacer el mix de lo mejor de un lenguaje de marcado como XML con la sencillez de un fichero de propiedades, ejemplo:

# config.xml

<config>
  <init-param>
    <param-name>user</param-name>
    <param-value>${user}</param-value>
  </init-param>
  <init-param>
    <param-name>password</param-name>
    <param-value>${password}</param-value>
  </init-param>
</config>

La idea detras de esto es incluir el valor de “user” y “password” (extraidos del fichero de propiedades) dentro del XML. Y aqui una implementación muy sencilla y generica que permite usar placeholders en cualquier InputStream, independiente del formato:

Leer más de esta entrada

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

%d personas les gusta esto: