developers journal – nivas,b:=log() https://www.nivas.hr/blog This is a blog from the Nivas.hr crew to the galaxy of unknown. Tue, 19 Sep 2017 12:50:51 +0000 en-US hourly 1 https://wordpress.org/?v=5.8.2 Measuring Disk IO Performance on MacOS https://www.nivas.hr/blog/2017/09/19/measuring-disk-io-performance-macos/ https://www.nivas.hr/blog/2017/09/19/measuring-disk-io-performance-macos/#comments Tue, 19 Sep 2017 12:50:51 +0000 https://www.nivas.hr/blog/?p=2573 Over time and numerous hardware updates around the office, I collected a vast number of 2.5″ HDD’s in my “hardware junk” box. The other day, I noticed two Kingston SSDNow V200 128GB SSD’s just sitting there doing nothing, so I decided to make them usable again. I have a really BAD track record of broken non-ssd 2.5″ travelling external disks. 99% of them broke or started showing serious problems just after 1st year of usage (traveling with them with the notebook). I wanted to see how will SSD disk act in same conditions.

I visited my local hardware store to get USB3 2.5″ HDD enclosure, being geek, I did my homework and decided to get noname enclosure for 15 EUR with semi rubber protection.
Good lady at the counter suggested that instead of 15EUR one, I get 13EUR noname enclosure since “it was better”.

Sceptical that I am, I bought both and decided to do a test and prove her that she is wrong. The one with higher price had to be better. :)

After fitting disks in enclosures, first issue I stumbled upon was a lack of disk benchmarking tool on MacOS. On Windows I used hdtune for ages and was happy with it. On MacOS however, Blackmagic Disk Speed Test in Mac App Store did not inspire confidence in me (blac kmagic, cmon?), not did 11yrs old Xbench or jDiskMark beta (written in Java).

In Ubuntu/Debian/RHEL land I’ve benchmarked device IO before and had good experience with FIO. FIO is a popular tool for measuring IOPS on a Linux servers.


Do not make mistake of benchmarking (or using dd for eg.) /dev/disk device.
On MacOS you should always use /dev/rdisk device.

/dev/disk – buffered access, for kernel filesystem calls, broken in 4kb chunks. goes more expensive root.
/dev/rdisk – “raw” in the BSD sense and force block-aligned I/O. Those devices are closer to the physical disk than the buffered cache ones.
If you do a read or write larger than one sector to /dev/rdisk, that request will be passed straight through. The lower layers may break it up (eg., USB breaks it up into 128KB pieces due to the maximum payload size in the USB protocol), but you generally can get bigger and more efficient I/Os. When streaming, like via dd, 128KB to 1MB are pretty good sizes to get near-optimal performance on current non-RAID hardware. (source)

1. Install FIO

brew install fio

2. Check correct disk number

diskutil list

Everything from this step forward can and will delete data on your disk. So BE VERY CAREFUL on which disk you use. You have been warned.

3. Precondition SSD
We precondition each drive the same way for each measurement, and stimulate the drive to the same performance state so the test process is deterministic

sudo dd if=/dev/zero of=/dev/rdisk2 bs=1m

4. Running tests

Random read/write performance

./fio --randrepeat=1 --ioengine=posixaio --direct=1 --gtod_reduce=1 --name=test --filename=test --bs=4k --iodepth=64 --size=4G --readwrite=randrw --rwmixread=75

Random read performance

./fio --randrepeat=1 --ioengine=posixaio --direct=1 --gtod_reduce=1 --name=test --filename=test --bs=4k --iodepth=64 --size=4G --readwrite=randread

Random write performance

./fio --randrepeat=1 --ioengine=posixaio --direct=1 --gtod_reduce=1 --name=test --filename=test --bs=4k --iodepth=64 --size=4G --readwrite=randwrite

(On MacOS we must use posixaio ioengine. If you are on running some different flavour of Unix just replace –ioengine=posixaio with eg. –ioengine=libaio for Ubuntu)

5. The results

The lady at the store was right! Using same HDD’s the cheaper HDD enclosure gave us better results. It was faster by almost 35%.

tray

read mb/s write mb/s read IOPS write IOPS
ASMT (/dev/disk)

10.9MiB/s 11.9MiB/s 86 IOPS 94 IOPS
ASMT

69.7MiB/s 72.8MiB/s 552 IOPS 576 IOPS
PATRIOT

92.4MiB/s 93.5MiB/s 738 IOPS 747 IOPS

If you are interested in values I got, here there are.

The first set of benchmarks (done on buffered /dev/disk device) revealed really poor performance [r=10.9MiB/s,w=11.9MiB/s][r=86,w=94 IOPS].

sudo fio --filename=/dev/disk2 --direct=1 --rw=randrw --rwmixwrite=50 --refill_buffers --norandommap --randrepeat=0 --ioengine=posixaio --bs=128k --rate_iops=1280  --iodepth=16 --numjobs=1 --time_based --runtime=86400 --group_reporting --name=benchtest
fio-2.18
Starting 1 thread
^Cbs: 1 (f=1), 0-2560 IOPS: [m(1)][0.5%][r=10.9MiB/s,w=11.9MiB/s][r=86,w=94 IOPS][eta 23h:52m:35s]
fio: terminating on signal 2

benchtest: (groupid=0, jobs=1): err= 0: pid=3075: Fri Mar 24 20:14:55 2017
   read: IOPS=94, BW=11.8MiB/s (12.4MB/s)(5234MiB/445379msec)
    slat (usec): min=0, max=303, avg= 0.40, stdev= 2.28
    clat (msec): min=47, max=228, avg=100.40, stdev=14.81
     lat (msec): min=47, max=228, avg=100.40, stdev=14.81
    clat percentiles (msec):
     |  1.00th=[   74],  5.00th=[   82], 10.00th=[   85], 20.00th=[   90],
     | 30.00th=[   93], 40.00th=[   96], 50.00th=[   98], 60.00th=[  102],
     | 70.00th=[  105], 80.00th=[  111], 90.00th=[  119], 95.00th=[  127],
     | 99.00th=[  151], 99.50th=[  161], 99.90th=[  184], 99.95th=[  192],
     | 99.99th=[  208]
  write: IOPS=94, BW=11.8MiB/s (12.4MB/s)(5237MiB/445379msec)
    slat (usec): min=0, max=296, avg= 0.53, stdev= 2.81
    clat (msec): min=25, max=177, avg=69.66, stdev= 9.52
     lat (msec): min=25, max=177, avg=69.66, stdev= 9.52
    clat percentiles (msec):
     |  1.00th=[   51],  5.00th=[   58], 10.00th=[   61], 20.00th=[   63],
     | 30.00th=[   66], 40.00th=[   68], 50.00th=[   69], 60.00th=[   71],
     | 70.00th=[   73], 80.00th=[   76], 90.00th=[   80], 95.00th=[   86],
     | 99.00th=[  105], 99.50th=[  114], 99.90th=[  133], 99.95th=[  137],
     | 99.99th=[  151]
    lat (msec) : 50=0.44%, 100=76.81%, 250=22.76%
  cpu          : usr=0.46%, sys=0.41%, ctx=283619, majf=3, minf=6
  IO depths    : 1=0.1%, 2=0.1%, 4=0.1%, 8=50.0%, 16=50.0%, 32=0.0%, >=64=0.0%
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     complete  : 0=0.0%, 4=98.3%, 8=1.7%, 16=0.1%, 32=0.0%, 64=0.0%, >=64=0.0%
     issued rwt: total=41875,41894,0, short=0,0,0, dropped=0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=16

Run status group 0 (all jobs):
   READ: bw=11.8MiB/s (12.4MB/s), 11.8MiB/s-11.8MiB/s (12.4MB/s-12.4MB/s), io=5234MiB (5489MB), run=445379-445379msec
  WRITE: bw=11.8MiB/s (12.4MB/s), 11.8MiB/s-11.8MiB/s (12.4MB/s-12.4MB/s), io=5237MiB (5491MB), run=445379-445379msec

Repeated benchmark on same enclosure, but using raw device (/dev/rdisk) revealed much nicer numbers – 600% faster than buffered device
[m(1)][0.3%][r=69.7MiB/s,w=72.8MiB/s][r=552,w=576 IOPS][eta 23h:55m:54s]

sudo fio --filename=/dev/rdisk2 --direct=1 --rw=randrw --rwmixwrite=50 --refill_buffers --norandommap --randrepeat=0 --ioengine=posixaio --bs=128k --rate_iops=1280  --iodepth=16 --numjobs=1 --time_based --runtime=86400 --group_reporting --name=benchtest
fio-2.18
Starting 1 thread
^Cbs: 1 (f=1), 0-2560 IOPS: [m(1)][0.3%][r=69.7MiB/s,w=72.8MiB/s][r=552,w=576 IOPS][eta 23h:55m:54s]
fio: terminating on signal 2

benchtest: (groupid=0, jobs=1): err= 0: pid=3075: Fri Mar 24 21:13:39 2017
   read: IOPS=538, BW=67.3MiB/s (70.6MB/s)(16.2GiB/245308msec)
    slat (usec): min=0, max=47, avg= 0.45, stdev= 1.02
    clat (msec): min=8, max=45, avg=15.05, stdev= 2.70
     lat (msec): min=8, max=45, avg=15.05, stdev= 2.70
    clat percentiles (usec):
     |  1.00th=[11200],  5.00th=[12224], 10.00th=[12736], 20.00th=[13376],
     | 30.00th=[13888], 40.00th=[14400], 50.00th=[14784], 60.00th=[15168],
     | 70.00th=[15680], 80.00th=[16320], 90.00th=[17280], 95.00th=[18048],
     | 99.00th=[23936], 99.50th=[36608], 99.90th=[39680], 99.95th=[40192],
     | 99.99th=[42240]
  write: IOPS=538, BW=67.4MiB/s (70.7MB/s)(16.2GiB/245308msec)
    slat (usec): min=0, max=65, avg= 0.46, stdev= 0.67
    clat (msec): min=6, max=45, avg=14.56, stdev= 2.71
     lat (msec): min=6, max=45, avg=14.57, stdev= 2.71
    clat percentiles (usec):
     |  1.00th=[10560],  5.00th=[11712], 10.00th=[12224], 20.00th=[12864],
     | 30.00th=[13376], 40.00th=[13888], 50.00th=[14272], 60.00th=[14784],
     | 70.00th=[15168], 80.00th=[15808], 90.00th=[16768], 95.00th=[17536],
     | 99.00th=[23680], 99.50th=[36096], 99.90th=[39168], 99.95th=[40192],
     | 99.99th=[42240]
    lat (msec) : 10=0.22%, 20=98.34%, 50=1.44%
  cpu          : usr=3.48%, sys=2.40%, ctx=531264, majf=3, minf=5
  IO depths    : 1=0.1%, 2=0.1%, 4=0.1%, 8=50.0%, 16=50.0%, 32=0.0%, >=64=0.0%
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     complete  : 0=0.0%, 4=97.9%, 8=1.8%, 16=0.3%, 32=0.0%, 64=0.0%, >=64=0.0%
     issued rwt: total=132027,132160,0, short=0,0,0, dropped=0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=16

Run status group 0 (all jobs):
   READ: bw=67.3MiB/s (70.6MB/s), 67.3MiB/s-67.3MiB/s (70.6MB/s-70.6MB/s), io=16.2GiB (17.4GB), run=245308-245308msec
  WRITE: bw=67.4MiB/s (70.7MB/s), 67.4MiB/s-67.4MiB/s (70.7MB/s-70.7MB/s), io=16.2GiB (17.4GB), run=245308-245308msec

Finally, the second HDD tray I benchmarked revealed best results, almost 35% faster than cheap-enclosure-1.
[m(1)][0.5%][r=92.4MiB/s,w=93.5MiB/s][r=738,w=747 IOPS][eta 23h:52m:50s]

sudo fio --filename=/dev/rdisk3 --direct=1 --rw=randrw --rwmixwrite=50 --refill_buffers --norandommap --randrepeat=0 --ioengine=posixaio --bs=128k --rate_iops=1280  --iodepth=16 --numjobs=1 --time_based --runtime=86400 --group_reporting --name=benchtest
fio-2.18
Starting 1 thread
^Cbs: 1 (f=1), 0-2560 IOPS: [m(1)][0.5%][r=92.4MiB/s,w=93.5MiB/s][r=738,w=747 IOPS][eta 23h:52m:50s]
fio: terminating on signal 2

benchtest: (groupid=0, jobs=1): err= 0: pid=3075: Fri Mar 24 20:37:26 2017
   read: IOPS=761, BW=95.2MiB/s (99.8MB/s)(39.2GiB/430198msec)
    slat (usec): min=0, max=310, avg= 0.55, stdev= 2.23
    clat (msec): min=1, max=48, avg=11.43, stdev= 2.84
     lat (msec): min=1, max=48, avg=11.43, stdev= 2.84
    clat percentiles (usec):
     |  1.00th=[ 6880],  5.00th=[ 8256], 10.00th=[ 8896], 20.00th=[ 9536],
     | 30.00th=[10048], 40.00th=[10560], 50.00th=[11072], 60.00th=[11584],
     | 70.00th=[12224], 80.00th=[12864], 90.00th=[14016], 95.00th=[15296],
     | 99.00th=[22912], 99.50th=[28800], 99.90th=[35584], 99.95th=[37120],
     | 99.99th=[40704]
  write: IOPS=762, BW=95.3MiB/s (99.9MB/s)(40.3GiB/430198msec)
    slat (usec): min=0, max=767, avg= 0.96, stdev= 3.58
    clat (usec): min=492, max=45310, avg=9422.63, stdev=2869.71
     lat (usec): min=493, max=45311, avg=9423.59, stdev=2869.68
    clat percentiles (usec):
     |  1.00th=[ 5024],  5.00th=[ 6240], 10.00th=[ 6944], 20.00th=[ 7712],
     | 30.00th=[ 8256], 40.00th=[ 8640], 50.00th=[ 9024], 60.00th=[ 9536],
     | 70.00th=[10048], 80.00th=[10688], 90.00th=[11712], 95.00th=[13120],
     | 99.00th=[21888], 99.50th=[27264], 99.90th=[35072], 99.95th=[37120],
     | 99.99th=[40704]
    lat (usec) : 500=0.01%
    lat (msec) : 2=0.01%, 4=0.08%, 10=49.48%, 20=49.08%, 50=1.35%
  cpu          : usr=4.59%, sys=2.86%, ctx=1256049, majf=0, minf=11
  IO depths    : 1=0.1%, 2=0.1%, 4=0.1%, 8=57.4%, 16=42.6%, 32=0.0%, >=64=0.0%
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     complete  : 0=0.0%, 4=98.2%, 8=1.8%, 16=0.1%, 32=0.0%, 64=0.0%, >=64=0.0%
     issued rwt: total=327551,327861,0, short=0,0,0, dropped=0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=16

Run status group 0 (all jobs):
   READ: bw=95.2MiB/s (99.8MB/s), 95.2MiB/s-95.2MiB/s (99.8MB/s-99.8MB/s), io=39.2GiB (42.1GB), run=430198-430198msec
  WRITE: bw=95.3MiB/s (99.9MB/s), 95.3MiB/s-95.3MiB/s (99.9MB/s-99.9MB/s), io=40.3GiB (42.1GB), run=430198-430198msec

Conclusion
fio is pretty robust utility for io testing. Beware of quality of onboard electronics when buying HDD trays. Trays within same price range, can vary 15-30% in speed.

]]>
https://www.nivas.hr/blog/2017/09/19/measuring-disk-io-performance-macos/feed/ 2
What is MySQL STRAIGHT_JOIN and when to use it? https://www.nivas.hr/blog/2017/08/18/mysql-straight_join-use/ https://www.nivas.hr/blog/2017/08/18/mysql-straight_join-use/#respond Fri, 18 Aug 2017 14:48:05 +0000 https://www.nivas.hr/blog/?p=2801 I carefully hand-craft my SQL queries and check them with an EXPLAIN statement. So it came as a surprise that my highly optimized SQL query became so sloooow. After short investigation I discovered that MySQL optimizer changes the order in which tables are joined. In most cases the optimizer is right. In my case it was also right at the beginning of the project lifetime – but later it reordered them in suboptimal order.

By using STRAIGHT_JOIN instead of “regular” inner join I made my sql query optimized again:

STRAIGHT_JOIN is an inner join where MySQL optimizer will not change the order of tables when joining them. It will always read the left table first and then the right table.

In general, you should  put to the left  the table which would give smaller result set in the final result.

Since premature optimization is the root of all evil, you should use “regular” inner join, monitor you application performance and check MySQL slow log. And if the sql query “suddenly” become slow – now you know how to fix it.

]]>
https://www.nivas.hr/blog/2017/08/18/mysql-straight_join-use/feed/ 0
Mysql GET_LOCK() “problem” … and how to debug it with help of mysql performance_schema https://www.nivas.hr/blog/2017/08/04/mysql-get_lock-problem-debug-help-mysql-performance_schema/ https://www.nivas.hr/blog/2017/08/04/mysql-get_lock-problem-debug-help-mysql-performance_schema/#comments Fri, 04 Aug 2017 11:57:03 +0000 https://www.nivas.hr/blog/?p=2778 After I recently switched back to Linux with php7 and mysql 5.7 I run into “bug” with MySQL GET_LOCK().
I executed php script which uses GET_LOCK(). Scripts were executed parallel (1-2s one after another) from two tabs of the same browser.
According to the output, locking was not working!!!

My first thought was that improvements which were introduced in MySQL 5.7. regarding GET_LOCK() broke something.

Test script:

<?php

$host = 'DB_HOST';
$db   = 'DB_NAME';
$user = 'DB_USER';
$pass = 'DB_PASSWD';
$charset = 'utf8mb4';

$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$opt = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
	PDO::ATTR_PERSISTENT => false
];
$pdo = new PDO($dsn, $user, $pass, $opt);

echo "<pre>";
echo "pid=(".getmypid().")\n";

$stmt = $pdo->query('SELECT GET_LOCK("foobar", 15)');
$row = $stmt->fetch();

var_dump($row);
echo "\n";

echo "Sleep for 50s\n";
sleep(50);

echo "</pre>";
?>

Lock timeout is 15s and script executes (and hold lock) for 50s so I could have time to start both scripts in parallel and monitor MySQL performance_schema.

Result from the first tab:

pid=(14769)
array(1) {
  ["GET_LOCK("foobar", 15)"]=>
  int(1)
}

Sleep for 50s

Result from the second tab:

pid=(14769)
array(1) {
  ["GET_LOCK("foobar", 15)"]=>
  int(1)
}

Sleep for 50s

Output is not as expected. GET_LOCK() from the second tab should return 0, not 1.

I executed the script again, but from the command line, again in parallel. Second one was executed 1-2s after the first one.

Result from the first (cmd line):

$ php -q locktest_1.php 

<pre>pid=(16328)
array(1) {
  ["GET_LOCK("foobar", 15)"]=>
  int(1)
}

Sleep for 50s

Result from the second (cmd line):

$ php -q locktest_1.php 

<pre>pid=(16382)
array(1) {
  ["GET_LOCK("foobar", 15)"]=>
  int(0)
}

Sleep for 50s

And everything was working as expected. First script obtained the lock, and second didn’t.

With helpful comments from StackOverflow community I continued with my investigation.

Put the performance_schema to good use

In order to see what is happening with my locks it is necessary to monitor performance_schema.threads and performance_schema.metadata_locks closely. To learn more about it check the MySQL documentation.

I added connection_id() in php test script in order to match running php script with entries in the performance_schema.threads and performance_schema.metadata_locks tables.

Second round of tests

Now, armed with all this data, second round of test can begin.

After running the test scripts I quickly executed these sql queries:

select * from performance_schema.threads;
select * from performance_schema.metadata_locks;

You will see active threads and locks and their status (GRANTED, PENDING).

First one returned:

pid=(14773)
CONNECTION_ID()=(71)
array(1) {
  ["GET_LOCK("foobar", 15)"]=>
  int(1)
}
Sleep for 50s

Second one returned:

pid=(14773)
CONNECTION_ID()=(72)
array(1) {
  ["GET_LOCK("foobar", 15)"]=>
  int(1)
}
Sleep for 50s

Also in performance_schema.threads there was at any point in time only one entry related to the test script.
First it showed:

Please note row with PROCESSLIST_ID = 71 (matching the output from the first script)

I kept refreshing performance_schema.threads and noticed that one thread related to the test script was replaced with another one. They were not running in parallel like they do when I start test script from cmd line.

Please note row with PROCESSLIST_ID = 72 (matching the output from the second script)
Also performance_schema.metadata_locks showed only one lock at any point in time (not one GRANTED and one PENDING):

For example, when script was executed in parallel from cmd line performance_schema.metadata_locks showed:

For now, it seems that locking is in fact working correctly, but the scripts executed from two tabs of the same browser are not being executed in parallel.

Third round of tests

I upgraded the test script to show start timestamp and end timestamp. This also proved that although I started the scripts with 1-2s delay, that they in fact were not running at the same time.

Final test script:

<?php

$host = 'DB_HOST';
$db   = 'DB_NAME';
$user = 'DB_USER';
$pass = 'DB_PASSWD';
$charset = 'utf8mb4';

$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$opt = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
	PDO::ATTR_PERSISTENT => false
];
$pdo = new PDO($dsn, $user, $pass, $opt);

echo "<pre>";

echo "start timestamp=(".date("c").")\n";

echo "pid=(".getmypid().")\n";

$stmt = $pdo->query('SELECT CONNECTION_ID() as connid');
$row = $stmt->fetch();

echo "CONNECTION_ID()=(".$row['connid'].")\n";

$stmt = $pdo->query('SELECT GET_LOCK("foobar", 15)');
$row = $stmt->fetch();

var_dump($row);
echo "\n";

echo "Sleep for 50s\n";
sleep(50);

echo "end timestamp=(".date("c").")\n";
echo "</pre>";
?>

First one returned:

start timestamp=(2017-08-03T14:30:48+00:00)
pid=(14768)
CONNECTION_ID()=(73)
array(1) {
  ["GET_LOCK("foobar", 15)"]=>
  int(1)
}
Sleep for 50s
end timestamp=(2017-08-03T14:31:38+00:00)

Second one returned:

start timestamp=(2017-08-03T14:31:38+00:00)
pid=(14768)
CONNECTION_ID()=(74)
array(1) {
  ["GET_LOCK("foobar", 15)"]=>
  int(1)
}
Sleep for 50s
end timestamp=(2017-08-03T14:32:28+00:00)

Please note the end timestamp of the first and start timestamp of the second script.

Conclusion

Execution of the scripts was serialized. They definitely didn’t run in parallel and that was the reason why both succeeded to acquire the lock.

GET_LOCK() in MySQL 5.7 is still working as it should (plus the improvements). But even more important, I realized what a powerful tool Performance Schema is in debugging what at first appeared to be really weird bug.

]]>
https://www.nivas.hr/blog/2017/08/04/mysql-get_lock-problem-debug-help-mysql-performance_schema/feed/ 1
C# Delegates and Events https://www.nivas.hr/blog/2017/06/08/csharp-delegates-and-events/ https://www.nivas.hr/blog/2017/06/08/csharp-delegates-and-events/#respond Thu, 08 Jun 2017 16:30:20 +0000 https://www.nivas.hr/blog/?p=2598 When I started learning C# the difference and relation between delegate and event was not so clear to me. After checking few books and lots of googling I realized that I am not the only one. :)

Some resources were correct in their description but lacked the comprehensive examples, other were completely wrong or just very unclear.

In this article the same task will be implemented using only delegate, and then using both delegate and event. So the difference, relation between delegate and event and the advantage of using the event will be clear.

It is expected that reader of this article is familiar with basic C# and has some knowledge about delegates and events.

Delegate

A delegate is a type that represents references to methods with a particular parameter list and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature and return type. You can invoke (or call) the method through the delegate instance.

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/

Please note that delegates can be chained – on single call multiple methods will be called.

Event

Events enable a class or object to notify other classes or objects when something of interest occurs. The class that sends (or raises) the event is called the publisher and the classes that receive (or handle) the event are called subscribers.

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/

Task

We will create one lemming and three watchers that will monitor its health and respond as soon as health changes.

Delegate example

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace delegate_only
{
	public delegate void LemmingChanged(Lemming lemming);

	public class Lemming
	{
		private int health;

		public int Health
		{
			get { return health; }
			set
			{
				health = value;

				if (LemmingChanged != null)
				{
					LemmingChanged(this);
				}
			}
		}

		public LemmingChanged LemmingChanged;
	}

	class LemmingWatch
	{
		public void LemmingChangedHandler(Lemming lemming)
		{
			Console.WriteLine($"LemmingWatch lemming changed. Health=({lemming.Health})");
		}
	}

	class AnotherLemmingWatch
	{
		public void LemmingChangedHandler(Lemming Lemming)
		{
			Console.WriteLine($"AnotherLemmingWatch Lemming changed. Health=({Lemming.Health})");
		}
	}

	class YetAnotherLemmingWatch
	{
		public void LemmingChangedHandler(Lemming Lemming)
		{
			Console.WriteLine($"YetAnotherLemmingWatch Lemming changed. Health=({Lemming.Health})");
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			Lemming Lemming = new Lemming() { Health = 99 };

			LemmingWatch LemmingWatch = new LemmingWatch();
			AnotherLemmingWatch anotherLemmingWatch = new AnotherLemmingWatch();

			Lemming.LemmingChanged += LemmingWatch.LemmingChangedHandler;
			Lemming.LemmingChanged += anotherLemmingWatch.LemmingChangedHandler;

			Console.WriteLine("Change Health:");
			Lemming.Health = 80;

			Console.WriteLine("\n--\n");

			// LemmingChanged not encapsulated 
			Console.WriteLine("LemmingChanged not encapsuled: Loose previous chained methods by mistake:");

			YetAnotherLemmingWatch yetAnotherLemmingWatching = new YetAnotherLemmingWatch();
			Lemming.LemmingChanged = yetAnotherLemmingWatching.LemmingChangedHandler; // loosing previous chained methods - mistake '=' instead of '+='
			Console.WriteLine("Change Health:");
			Lemming.Health = 51;
			

			Console.ReadLine();
		}
	}
}

Output

Change Health:
LemmingWatch lemming changed. Health=(80)
AnotherLemmingWatch Lemming changed. Health=(80)

--

LemmingChanged not encapsuled: Loose previous chained methods by mistake:
Change Health:
YetAnotherLemmingWatch Lemming changed. Health=(51)

As you can see delegate is public – for now, this is necessary in order to enable adding event handler from outside of the class.

public LemmingChanged LemmingChanged;

Two event handlers are successfully added:

Lemming.LemmingChanged += LemmingWatch.LemmingChangedHandler;
Lemming.LemmingChanged += anotherLemmingWatch.LemmingChangedHandler;

But there is mistake when adding the third one – and two previously added event handlers are removed by simple typing mistake (= instead of +=):

Lemming.LemmingChanged = yetAnotherLemmingWatching.LemmingChangedHandler;

Also since delegate is public – it is possible to evoke the event from the outside of the class and evoke event handlers although requirements for raising the event are not met.

Event example

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace delegate_with_event
{
	public delegate void LemmingChanged(Lemming lemming);

	public class Lemming
	{
		private int health;

		public int Health
		{
			get { return health; }
			set
			{
				health = value;

				if (lemmingChanged != null)
				{
					lemmingChanged(this); // use private delegate
				}
			}
		}

		private LemmingChanged lemmingChanged; // changed to private

		// use event to add and remove event handler to/from delegate
		public event LemmingChanged LemmingChanged
		{
			add
			{
				lemmingChanged += value;
			}
			remove
			{
				lemmingChanged -= value;
			}

		}


	}

	class LemmingWatch
	{
		public void LemmingChangedHandler(Lemming lemming)
		{
			Console.WriteLine($"LemmingWatch lemming changed. Health=({lemming.Health})");
		}
	}

	class AnotherLemmingWatch
	{
		public void LemmingChangedHandler(Lemming Lemming)
		{
			Console.WriteLine($"AnotherLemmingWatch Lemming changed. Health=({Lemming.Health})");
		}
	}

	class YetAnotherLemmingWatch
	{
		public void LemmingChangedHandler(Lemming Lemming)
		{
			Console.WriteLine($"YetAnotherLemmingWatch Lemming changed. Health=({Lemming.Health})");
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			Lemming Lemming = new Lemming() { Health = 99 };

			LemmingWatch LemmingWatch = new LemmingWatch();
			AnotherLemmingWatch anotherLemmingWatch = new AnotherLemmingWatch();

			Lemming.LemmingChanged += LemmingWatch.LemmingChangedHandler;
			Lemming.LemmingChanged += anotherLemmingWatch.LemmingChangedHandler;

			Console.WriteLine("Change Health:");
			Lemming.Health = 80;

			Console.WriteLine("\n--\n");

			// LemmingChanged not encapsulated 
			Console.WriteLine("Add 3rd event handler:");

			YetAnotherLemmingWatch yetAnotherLemmingWatching = new YetAnotherLemmingWatch();
			// impossible to make mistake: C# error: event Lemming.LemmingChanged can only appear on the left hand side of += -=
			//Lemming.LemmingChanged = yetAnotherLemmingWatching.LemmingChangedHandler; 
			Lemming.LemmingChanged += yetAnotherLemmingWatching.LemmingChangedHandler;
			Console.WriteLine("Change Health:");
			Lemming.Health = 51;


			Console.ReadLine();
		}
	}
}

Output

 Change Health:
 LemmingWatch lemming changed. Health=(80)
 AnotherLemmingWatch Lemming changed. Health=(80)

--

Add 3rd event handler:
 Change Health:
 LemmingWatch lemming changed. Health=(51)
 AnotherLemmingWatch Lemming changed. Health=(51)
 YetAnotherLemmingWatch Lemming changed. Health=(51)

Delegate is now private:

private LemmingChanged lemmingChanged;

Using event to add or remove event handler to or from delegate is very similar as using properties to get and set value to member variables.

Just instead of set/get,  add/remove is used. :)

public event LemmingChanged LemmingChanged
{
	add
	{
		lemmingChanged += value;
	}
	remove
	{
		lemmingChanged -= value;
	}
}

Adding and removing the event handlers is done through event. It is not possible to access delegate directly from outside of the class.

And if we try to clear previously added event handlers by using = instead of += as in previous example – it is just not possible:

Lemming.LemmingChanged = yetAnotherLemmingWatching.LemmingChangedHandler; 

C# will report error:

event Lemming.LemmingChanged can only appear on the left hand side of += -=

Conclusion

Events are heavily relay on delegates. In fact relationship between events and delegates is very similar as relationship between properties and member variables. Events provide safe and friendly way of using delegates.

They enable adding and removing event handlers from the outside of the class without allowing bad things to happen. Using events it is not possible to clear all event handlers by mistake or to raise the event from outside of the class (this is important because it is class responsibility to invoke the delegate when some class internal conditions are met).

By using events as a “wrapper” for delegate you encapsulation is not broken and you keep the needed flexibility. You can have you cake an eat it. :)

]]>
https://www.nivas.hr/blog/2017/06/08/csharp-delegates-and-events/feed/ 0
Install vpnc on MacOS Sierra https://www.nivas.hr/blog/2017/02/15/install-vpnc-macos-sierra/ https://www.nivas.hr/blog/2017/02/15/install-vpnc-macos-sierra/#comments Wed, 15 Feb 2017 14:06:10 +0000 https://www.nivas.hr/blog/?p=2564 Update: 3.4.2017. this no longer works since homebrew/boneyard got obsoleted/deleted today.

If you try to install Cisco VPN concentrator client (vpnc) today, chances are you won’t be able to since it was removed from regular homebrew formulae.

brew install vpnc
Error: No available formula with the name "vpncc"
==> Searching for similarly named formulae...
Error: No similarly named formulae found.
==> Searching taps...
Error: No formulae found in taps.

However you can still install it from archive of homebrew formulae (boneyard):

brew cask install tuntap
brew install homebrew/boneyard/vpnc

Be aware that this version of vpnc requires tuntap installed, also, it installs all this as dependencies:

brew deps vpnc
gmp
gnutls
libffi
libgcrypt
libgpg-error
libtasn1
libunistring
nettle
p11-kit

This will install last available, rather old vpnc version 0.5.3 dating back in 2006.

I found a new and modified version of vpnc on github modified for xnu (OS X 10.6+) and utun support so tuntap is not required. Unfortunately it is not yet in brew as formulae. However if you install all above dependencies, you will be able to build it easily your self:

git clone https://github.com/breiter/vpnc.git
cd vpnc
make
sudo make install

This will install vpnc version 0.5.3-xnu-2015-07-03.

Both vpnc versions expect configuration to be present in

/usr/local/etc/vpnc
]]>
https://www.nivas.hr/blog/2017/02/15/install-vpnc-macos-sierra/feed/ 1
Apache sending “Vary: Host” making things uncacheable for Varnish https://www.nivas.hr/blog/2017/02/13/apache-sending-vary-host-making-things-uncacheable-varnish/ https://www.nivas.hr/blog/2017/02/13/apache-sending-vary-host-making-things-uncacheable-varnish/#respond Mon, 13 Feb 2017 22:36:02 +0000 https://www.nivas.hr/blog/?p=2530 TLDR;
Using %{HTTP_HOST} in .htaccess, will cause Apache to included a “Vary: Host” field in response.
Subsequently “Vary: Host” header from Apache will force Varnish not to cache otherwise cacheable content.

HTTP Vary is not a trivial concept. It is by far the most misunderstood HTTP header. (Varnish Docs)

On a project I’ve been working, I could not make Varnish hard cache the site no matter what I did.
It was mostly read-only site and I wanted to achieve that in case of backend failures – site would still run from the cache. To avoid surprises, I was using my own configuration template which was working exactly as I wanted it on different project.

But, no matter what I did – Varnish was not caching everything. Instead I got ‘per-user‘ cache: if user visited eg, homepage, in case of backend failure – user could reload homepage and Varnish would serve homepage from stale cache to the user. Visiting any other page user did not visit before backend failure (and therefore not in his cache), would result in Varnish freaking that backend is down.

What I noticed was strangely large Vary-Header in Varnish response

Vary:Host,Accept-Encoding,User-Agent

After spending hours of tunneling, debugging Varnish configuration, analysing header responses, comparing server configurations, hunting for cookies that could have been somehow magically slip through… last place I looked was my main .htaccess. I was using mod_deflate there, but as it checked out everything was fine.

On a side note, I have been experimenting with per host configuration in .htaccess, so Basic Authentication would not kick in dev enviroment. It was working great up until now, and it goes something like this:

<if "%{HTTP_HOST} == 'dev.nivas.hr'">

Require valid-user
...
Allow from facebook.com

</if>

What I had to find out the hard way, is that if special Apache enviroment variable %{HTTP_HOST} was used in a eg. .htaccess, Apache would change response header. Server was returning a header which included a “Vary: Host” field, which means that the server didn’t serve a regular static page, but its reply depends on the “Host” field in the HTTP request. Browsers interpret this as “the content returned is dynamic, don’t cache it (source).

curl -I http://localhost/
HTTP/1.1 200 OK
Date: Mon, 13 Feb 2017 14:48:14 GMT
Server: Apache/2.4.6 (CentOS)
Vary: Host,User-Agent
Content-Type: text/html; charset=UTF-8

It is really strange I did not hit this before because RewriteCond can add “Host” to the Vary-Header as well. eg. a RewriteCond that evaluates %{HTTP_HOST} automatically adds “Host” to the Vary-Header. This is unnecessary and not permitted according to https://tools.ietf.org/html/rfc7231#section-7.1.4. The issue was reported and has been sitting in Apache bugtraq for a while without clear resolution.

I can understand why Varnish is not caching, but cannot understand Apache logic. I do have VirtualHost defined, so therefore my request do vary on Host. There is no need in forcing this out in response.

After removing %{HTTP_HOST} from .htaccess, site was cacheable as we wanted.

HTTP/1.1 200 OK
Date: Mon, 13 Feb 2017 22:18:33 GMT
Content-Type: text/html; charset=UTF-8
Vary: Accept-Encoding
Age: 3707
X-Nivas-Crew: loves you :) https://www.nivas.hr
X-Backend: backend_app1
X-Cache: HIT
X-Cache-Hits: 786332
X-Vudu-Url-Cache: hit

Don’t forget to normalize your Vary in Varnish, chance are without normalization it will never see a cache hit.

Happy caching!

Have a cool Varnish project you need help on? Contact us.

]]>
https://www.nivas.hr/blog/2017/02/13/apache-sending-vary-host-making-things-uncacheable-varnish/feed/ 0
Proper way to include Facebook SDK for Javascript and jQuery … https://www.nivas.hr/blog/2016/10/29/proper-way-include-facebook-sdk-javascript-jquery/ https://www.nivas.hr/blog/2016/10/29/proper-way-include-facebook-sdk-javascript-jquery/#comments Sat, 29 Oct 2016 14:14:36 +0000 https://www.nivas.hr/blog/?p=2514 … and avoid race condition! :)

Although fb is pretty clear on how to do it (http://bit.ly/2eGfFDv) – many developers are still doing it wrong. So this blog post is here to be reminder for me and others, also it will propose small upgrade to the fb recommended practice.

The naive and wrong way to do it

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script>
window.fbAsyncInit = function()
	{
		FB.init({
			appId      : 'your-app-id',
			xfbml      : true,
			version    : 'v2.8'
			});
		
		// do something with DOM and jQuery
		$('#loginbutton,#feedbutton').removeAttr('disabled');
		FB.getLoginStatus(updateStatusCallback);
	};

  (function(d, s, id){
     var js, fjs = d.getElementsByTagName(s)[0];
     if (d.getElementById(id)) {return;}
     js = d.createElement(s); js.id = id;
     js.src = "//connect.facebook.net/en_US/sdk.js";
     fjs.parentNode.insertBefore(js, fjs);
   }(document, 'script', 'facebook-jssdk'));
</script>

The problem with this is the race condition: at the time fbAsyncInit gets called there is no guaranty that DOM is ready and that #loginbutton or #feedbutton is there, ready to be used.

This bug is hard to spot and debug – since it will not always manifest. It is very likely that you as developer with fast connection and computer will never experience this bug – but your clients (visitors) might.

meme

So it is best not to leave this to chance since fix is so easy.

Facebook recommendation

Learn more: http://bit.ly/2eGfFDv

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script>
$(document).ready(function()
{
	$.ajaxSetup({ cache: true });
	
	$.getScript('//connect.facebook.net/en_US/sdk.js',
				function()
				{
					FB.init({
					appId: '{your-app-id}',
					version: 'v2.8'
				}
			);

		$('#loginbutton,#feedbutton').removeAttr('disabled');
		FB.getLoginStatus(updateStatusCallback);
	});
});
</script>

Problem with this approach is that you are changing global jQuery ajax cache settings.

The better way

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script>
$(document).ready(function()
{
	$.ajax(
			{
				url: '//connect.facebook.net/en_US/sdk.js',
				dataType: 'script',
				cache: true,
				success:function(script, textStatus, jqXHR)
				{
					FB.init(
						{
							appId      : '{your-app-id}',
							xfbml      : true,
							version    : 'v2.8'
						}
					);
					
					$('#loginbutton,#feedbutton').removeAttr('disabled');
					FB.getLoginStatus(updateStatusCallback);
				}
			});
});
</script>

Now we did it. No race condition between jQuery, fb SDK for JS and loading of DOM. Also global jQuery Ajax settings are untouched.

Cheers! :)

]]>
https://www.nivas.hr/blog/2016/10/29/proper-way-include-facebook-sdk-javascript-jquery/feed/ 1
UX fail: Crazy Crazy Egg create account https://www.nivas.hr/blog/2016/10/26/ux-fail-crazy-crazy-egg-create-account/ https://www.nivas.hr/blog/2016/10/26/ux-fail-crazy-crazy-egg-create-account/#respond Wed, 26 Oct 2016 14:20:02 +0000 https://www.nivas.hr/blog/?p=2506 This is Crazy Egg create account screen from my point of view:

sml_dsc_1304

I was quite amused by the empty mysterious input field. :)
But, being the web developer for a decade (at least) – I knew what needs to be done:
Dear Crazy Egg users – get down on your knees and take a look upwards, and voila:

crazy egg, ux fail

Mystery solved.

Also we tested this screen on several monitors, with slightly different points of view (we are diverse company in which developers range in height from 167 to 198cm) – in all cases content of the input field is not visible or at best barely visible.

So, do you want to share best UX fails you encountered?

]]>
https://www.nivas.hr/blog/2016/10/26/ux-fail-crazy-crazy-egg-create-account/feed/ 0
Google Chrome installer failed to start https://www.nivas.hr/blog/2014/01/23/google-chrome-installer-failed-to-start/ https://www.nivas.hr/blog/2014/01/23/google-chrome-installer-failed-to-start/#comments Thu, 23 Jan 2014 14:49:05 +0000 https://www.nivas.hr/blog/?p=2392 TL;DR Manually delete Chrome uninstaller left overs on Windows to get rid off “Google Chrome installer failed to start” error during reinstall.

bad-chrome-bad_250

Some time last week Google Chrome browser on all workstations (Win7 x64) I use got their preference files corrupted and as a result I lost all my bookmarks, extensions etc.

Internet paranoid as I am, first thing for me was to scan for viruses, and luckily did not find any (just some friendly AdWare my beloved Lenovo has preinstalled for me but that’s another post). Just in case – I uninstalled Chrome and cleaned up registry using ccleaner before I do a clean install. One restart later, I was heading to google.com with my ol’trusty Internet Explorer in order to download and run Chrome installer.

What happened next was beyond everything I was prepared for – Google Chrome was unable to install throwing a undescriptive message: “Installation failed. The Google Chrome installer failed to start.“. Tried everything. Restarting, cleaning left over Chrome stuff manually from disk and registry, downloading and running alternate (offline) Google Chrome installer. No luck. Each time this irritating graphics slammed me:
chrome-install-bug

The funny lol moment was reached when I googled for manual chrome uninstall:
uninstall

First result, on Google support forum https://support.google.com/chrome/answer/111899?hl=en results in a – Page not available. *FACEPALM*

After hours of wasting my time googling I started considering a Windows re-install.
But at the end, digging through registry paid off. Found additional ‘special’ key which nobody mentioned nowhere for x64 os (Wow6432Node). After deleting it, I was finally able to reinstall Chrome.

First thing I did after I reinstalled it – I run ccleaner and – corrupted Chrome settings. Luckily I could repeat uninstall/reinstall procedure knowing what I was forced to learn. :)

Hope it will help somebody.

Solution
To fix “Chrome installer failed to start” issue, you have to manually delete Chrome uninstaller left overs:
1. Delete “%APPDATA%\Google” folder and all of it’s contents
2. Delete following registry keys:
HKEY_LOCAL_MACHINE\SOFTWARE \Google\
HKEY_CURRENT_USER\SOFTWARE \Google\

And the most important – for 64-bit Windows, delete also
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Google\

3. If you by any chance use ccleaner or any similar utility for deleting your browser/system cache, and you update your browsers regularly – be 100% sure to always update the cleaning apps to newest versions to avoid corruption of your browsers configuration.

]]>
https://www.nivas.hr/blog/2014/01/23/google-chrome-installer-failed-to-start/feed/ 15
ZgPHP meetup conference 2013 https://www.nivas.hr/blog/2013/09/10/zgphp-meetup-conference-2013/ https://www.nivas.hr/blog/2013/09/10/zgphp-meetup-conference-2013/#respond Tue, 10 Sep 2013 10:23:41 +0000 https://www.nivas.hr/blog/?p=2380 On 14th of September (this Saturday) our friends from ZgPHP user group will held a second anniversary jubilee ZgPHP Meetup and a full blown one-day PHP conference. The conference will be held in Croatian chamber of commerce offices (HGK), Nova cesta 3-7 on the second floor (entrance is located on south side of the building, same entrance as Lidl). For further details check out the conference web site.

See you there!

logo

]]>
https://www.nivas.hr/blog/2013/09/10/zgphp-meetup-conference-2013/feed/ 0
Upgrade to Apache 2.4 and AddOutputFilterByType issue https://www.nivas.hr/blog/2012/08/10/upgrade-to-apache-2-4-and-addoutputfilterbytype-issue/ https://www.nivas.hr/blog/2012/08/10/upgrade-to-apache-2-4-and-addoutputfilterbytype-issue/#comments Fri, 10 Aug 2012 10:46:13 +0000 https://www.nivas.hr/blog/?p=2332 For quite some time now, we have been using Apache module mod_deflate to gzip some static content served by Apache automatically:

AddOutputFilterByType DEFLATE text/html text/css application/x-javascript

If you upgraded from Apache 2.2 to 2.4, you will notice error in your apache log:
Invalid command ‘AddOutputFilterByType’, perhaps misspelled or defined by a module not included in the server configuration.

To fix this, enable mod_filter and restart Apache. mod_filter is required in 2.4, although in 2.2 was not the case.

]]>
https://www.nivas.hr/blog/2012/08/10/upgrade-to-apache-2-4-and-addoutputfilterbytype-issue/feed/ 2
Beware of max_input_vars php ini configuration option https://www.nivas.hr/blog/2012/04/04/beware-of-max_input_vars-php-ini-configuration-option/ https://www.nivas.hr/blog/2012/04/04/beware-of-max_input_vars-php-ini-configuration-option/#comments Wed, 04 Apr 2012 18:02:08 +0000 https://www.nivas.hr/blog/?p=2324 If you are updating PHP on your production server, beware of relatively new max_input_vars php.ini directive which is now 1000 by default. That means if you have 1001 form field – only 1000 form fields will be submitted. Use of this directive mitigates the possibility of denial of service attacks which use hash collisions in connection with CVE-2011-4885.

From php changelog:

2012-01-03 : security / trunk - Added php-5.2-max-input-vars patch max_input_vars directive to prevent attacks based on hash collisions - CVE-2011-4885

Why we have so much form fields is a subject for different post. The main problem is that even php site says this update is available from PHP version 5.3.9. The fact is we have 5.3.2-1ubuntu4.14 and the update is there.

So… you know… beware. :)

]]>
https://www.nivas.hr/blog/2012/04/04/beware-of-max_input_vars-php-ini-configuration-option/feed/ 3
Excel stopped calculating formulas – Earth coming to an end? https://www.nivas.hr/blog/2012/03/20/excel-stopped-calculating-formulas-earth-coming-to-an-end/ https://www.nivas.hr/blog/2012/03/20/excel-stopped-calculating-formulas-earth-coming-to-an-end/#respond Tue, 20 Mar 2012 15:22:38 +0000 https://www.nivas.hr/blog/?p=2295 From early 60s to modern times, spreadsheet processors play important role in every man’s day life which basic functionality is taken for granted.

In the accounting a “spread sheet” was and is a large sheet of paper with columns and rows that lays everything out about transactions for a business person to examine. An electronic spreadsheet organizes information into columns and rows. The data can then be “added up” by a formula to give a total or sum. The spreadsheet program summarizes information from many sources in one place and presents the information.
If you are wondering who are those two cool spreadsheet hipsters, check out A Brief History of Spreadsheets (here and here). Now, let’s go back to 2012. Erm 2007.

Did your Excel (2007) stopped auto calculating your precious formulas out of the sudden and you are in a hurry to send cost estimates to your client? Just like mine did? Yes, that can be nasty. :)

  1. Click on the Formulas ribbon tab,
  2. then select Calculation Options,
  3. and then check if Automatic is on.

Simple eh? How did this switch off in first place? No idea. This applies to Excel 2007, dunno about 2012:

]]>
https://www.nivas.hr/blog/2012/03/20/excel-stopped-calculating-formulas-earth-coming-to-an-end/feed/ 0
Alarm and Bluetooth icons in iOS https://www.nivas.hr/blog/2012/03/13/alarm-and-bluetooth-icons-in-ios/ https://www.nivas.hr/blog/2012/03/13/alarm-and-bluetooth-icons-in-ios/#comments Tue, 13 Mar 2012 14:31:42 +0000 https://www.nivas.hr/blog/?p=2275 I was looking at the iPhone (iOS) top system bar and one thing was bothering me in particular, but I could not place my finger on it. Yesterday I figured it out. Alarm icon, and rarely, Bluetooth icon.

If we segment the top bar into three pieces (left, central, right) each of which has a particular info to convey, the structure is pretty clean. On the left there is info about connectivity, signal strength, wireless, 3G etc. Middle part is reserved for time, and on the right there is battery info.

The problem starts when additional icons show up, in particular Alarm and Bluetooth. The Bluetooth icon is an obvious candidate to be moved on the left side since all the connectivity is there. There is no reason to dislocate Bluetooth to the right.

Alarm icon has two problems.

First, an icon itself. Clock. Clock is a wrong communication on many levels. I can clearly remember few years ago when I first got iPhone that this icon meant nothing to me. I set the Alarm and had no idea a Clock icon means that I have an alarm setup. Throughout user interfaces of the world alarm was mostly represented by a ringing bell. This not only is a better visual communication but also is a logical translation of the ancient real-life alarms (church bells) into digital era.

Second problem is the position. When a clock icon appears next to battery icon, what does that mean? It means nothing, exactly. Alarm icon must be placed next to time, because they are both time related bits of information.

Additional Hate
While doing this I found out this little bit of not-so-perfect design. Three icons, each of them has different shade of grayish color. How did Steve miss that one out? Also, minute-hand is not centered. My OCD is kicking in now, I have to stop dissecting these icons.

]]>
https://www.nivas.hr/blog/2012/03/13/alarm-and-bluetooth-icons-in-ios/feed/ 4
Non-breaking white space Internet Explorer 8 JavaScript regexp bug (and how to fix it) https://www.nivas.hr/blog/2012/01/19/non-breaking-white-space-in-internet-explorer-8-bug-and-how-to-fix-it/ https://www.nivas.hr/blog/2012/01/19/non-breaking-white-space-in-internet-explorer-8-bug-and-how-to-fix-it/#comments Thu, 19 Jan 2012 10:48:25 +0000 https://www.nivas.hr/blog/?p=2218 While developing jQuery plugin for upcoming bookmarking “Items in select boxes” plugin for our Vudu CMS

I wrote a simple regexp to strip few characters (pipe, minus, apostrof and white-space) that are added before the actual item.

 function cleanOptionText(txt)
 {
 return txt.replace(/^[\s|'-]+/, '');
 };

It worked just fine on FF9 and Chrome but in IE8 only the first pipe (|) was removed. After some debugging I discovered that I have both spaces and non-breaking spaces that should be removed and that in IE8 class shorthand \s (which should include all white space) doesn’t include non-breaking space.

Code for non-breaking space is 0xa0 (dec 160) so regexp should be updated as follows:

function cleanOptionText(txt)
{
return txt.replace(/^[\s\xA0|'-]+/, '');
};

Also we took opportunity to update our javascript trim functions:

String.prototype.trim = function()
{
return this.replace(/^[\s\xA0]+|[\s\xA0]+$/g,"");
}

String.prototype.ltrim = function()
{
return this.replace(/^[\s\xA0]+/g,"");
}

String.prototype.rtrim = function()
{
return this.replace(/[\s\xA0]+$/g,"");
}

Here is complete html test file:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>nbsp test</title>
  </head>
  <body>
    <form action="#">
      <fieldset>
      <select id="testselect">
        <option value="1">&nbsp; &nbsp; Some option</option>
      </select>
      </fieldset>
    </form>

    <script type="text/javascript">
      /*<![CDATA[*/

      var txt=document.getElementById('testselect').options[0].text;

      var analyizeTxt='';
      var l=txt.length;
      for(var i=0; i<l; i++)
      {
        analyizeTxt+= '[ ('+txt.charCodeAt(i)+')=('+txt.charAt(i)+')]';
      }

      alert(analyizeTxt);

      var newTxt1 = txt.replace(/^\s+/, '');
      alert('Using only \\s = ('+newTxt1+')');


      var newTxt2 = txt.replace(/^[\s\xA0]+/, '');
      alert('Using \\s and \\xA0= ('+newTxt2+')');
      /*]]>*/
    </script>
  </body>
</html>

]]>
https://www.nivas.hr/blog/2012/01/19/non-breaking-white-space-in-internet-explorer-8-bug-and-how-to-fix-it/feed/ 1
Ubuntu Cloud Live on OpenStack https://www.nivas.hr/blog/2012/01/19/ubuntu-cloud-live-on-openstack/ https://www.nivas.hr/blog/2012/01/19/ubuntu-cloud-live-on-openstack/#respond Thu, 19 Jan 2012 09:12:00 +0000 https://www.nivas.hr/blog/?p=2207 crm.com published selection of the 10 Best Open-Source Products Of 2011. On 8th place, you can find OpenStack (an open-source cloud platform) for whom our dear coleague Ante Karamatić is leading Ubuntu Cloud Live project. We are looking forward to new interesting grounds cloud support will bring us in 2012.

Ante explained: “Ubuntu Cloud is a product. Ubuntu Live Cloud is a custom version of Ubuntu Cloud, customized to work without a disk. OpenStack is a major, if not essential, part of the Ubuntu Cloud.”

]]>
https://www.nivas.hr/blog/2012/01/19/ubuntu-cloud-live-on-openstack/feed/ 0
rim – PHP Remote Image Library https://www.nivas.hr/blog/2012/01/16/rim-php-remote-image-library/ https://www.nivas.hr/blog/2012/01/16/rim-php-remote-image-library/#comments Mon, 16 Jan 2012 13:35:47 +0000 https://www.nivas.hr/blog/?p=2174 Working on recent home project I found there is no library in PHP to get dimensions of remote images.
So I made rim php library to get type and size of remote images in optimized way.

Fork it on github

The easiest way of getting a image dimension is by using getimagesize PHP function.
It has one great fault when working with remote images, whole image must be downloaded in order for getimagesize to read it’s dimensions.
So if you want to find out dimensions of jpeg 2MB in size getimagesize will first download it. There goes bandwidth!

Rim downloads only few byes need to determine image type and dimensions.
Also if you need to get dimensions of lot’s of pictures rim will thread fetch request for further speed increase.

Take a look at this benchmark, fetching image types and sizes of all images on Hot New Releases in Books at amazon.com:

Have fun!

P.S.
Bug reports would be appreciated ;)

]]>
https://www.nivas.hr/blog/2012/01/16/rim-php-remote-image-library/feed/ 3
Why I, designer, do not use Mac https://www.nivas.hr/blog/2011/12/14/why-i-designer-do-not-use-mac/ https://www.nivas.hr/blog/2011/12/14/why-i-designer-do-not-use-mac/#comments Wed, 14 Dec 2011 09:51:19 +0000 https://www.nivas.hr/blog/?p=2170 I have been getting a lot of questions lately why I do not use Mac as my primary work machine. I really love iPhone and iPad, and people get confused when they see all of my (primary) work is produced on Windows PC. The confusion gets bigger when people see that I even use Apple Keyboard (wired, full scale one), but it is attached to the Windows machine.

So, in three (plus one) points, here it is: why I do not work on Mac.

a) Shortcuts are horrible for Photoshop work
I expect this to be true for most of the design work as well. If you do some typing and some basic image editing, you are probably fine. But when you need to get some real work done, the layout of shortcuts is bad.

Windows has main shortcut button (Control) on the far left side and is pressed with small pinky finger. Mac has main shortcut (Cmd) as a third key from the left next to space. This means that Copy/Paste/Cut and all of the other shortcuts are done by pressing thumb on Cmd and index finger on shortcut key.

This created few issues. First, as thumb needs to be on Cmd at all time so it cannot be on Space. And having thumb on space allows fast panning around canvas in Photoshop. Second, whole hand is too far away from the Tab key, and it is hard to press Ctrl Tab to switch open documents.

Windows shortcut layout enables me to:

– Use my pinky finger on Control in order to activate shortcuts
– Use my index finger to press activated shortcuts while pinky presses Control
– Use my thumb to press space and pan the canvas around
– Quickly access Control+Tab to switch open documents
– Quickly access Alt+Tab to switch between programs as the whole hand is positioned a bit to the left on the keyboard
– Quickly press Win+D to show Desktop

I have tried working on Mac, and I have seen other designers work on Mac. It is inefficient. It takes too long to do stuff.

b) No concept of TRUE full-screen
Pressing the green circle on Mac will cause the window to go a bit bigger, maybe even to fill the screen as much as possible. But this is not full-screen. You can still drag the window around, it is not hard docked to the edges and it works erratic. Some programs do not even fill entire screen (Chrome just resizes vertically to fill the height of the monitor). Full-screen Photoshop and other working applications are not really full-screen. You can always see desktop icons pop out from the background creating clutter. In Windows, when you go full-screen, you really go full-screen. Software is hard locked onto borders, border chrome goes away, you cannot drag the window, nothing sticks out from the background, … It’s just you and the software, mano-a-mano.

c) Universal top located toolbar.
For some folks this is great – having universal toolbar at the top through which you control active software. But realistically looking, that’s bad. It gets bad when you see that even some puny little software (calculator, media player,…) has that full size toolbar at the top. Why? Why not have those options located contextually in the software and be over with it? It is just confusing and wrong and dislocated from the main window of the software. It mentally keeps me thinking that the software is broken into pieces. The main window of Calculator is in the middle of the screen, then there is a huge gap, and then on top is Calculators toolbar. Wrong. In Windows each software is neatly packed block no matter how small it is. And in that block is the software’s toolbar, not dislocated somewhere far away.

+1) Small stuff.
There is tons of other small stuff which I will not go into, but all of that stuff point out to one similar common denominator – Mac is done to be beautiful, not efficient. Simple things point that out, like the fact that you cannot completely kill Window Minimize animation. It has to be either that swooooosh effect, or shrinking effect. There is no third option “no animation whatsoever”. This beauty over efficiency goes even outside of the software bounds and into hardware. I will point out one thing – mouse. The mouse shipping even with Mac Pro is Wireless Magic Mouse. Yes, it is beautiful. Yes, it is magical. And no, you cannot work with it for a prolonged period of time. It’s too small, the “feet” are not slippery so it is hard to move it around the mousepad, it’s not as responsive as wired mouse, etc… Apple has yet to make a good EFFICIENT mouse. And this tells you what their stance on effectiveness is. Screw effectiveness, stuff looks good sitting on the desk.

]]>
https://www.nivas.hr/blog/2011/12/14/why-i-designer-do-not-use-mac/feed/ 13
Twitter redesigns https://www.nivas.hr/blog/2011/12/09/twitter-redesigns/ https://www.nivas.hr/blog/2011/12/09/twitter-redesigns/#respond Fri, 09 Dec 2011 12:18:11 +0000 https://www.nivas.hr/blog/?p=2163 Yesterday Twitter got overhauled. I am not talking about some small, tiny redesign tweak; it got completely reworked from ground up. The overhaul also includes replacement terminology for old words. There are no more “mentions”, they are now called “Connect”. Hash tags are now called “Discover”. Many new things are introduced, and I will not comment on validity of those ideas. It is Twitter’s political decision to change terminology and how they present themselves to the world.

What I will comment on is the new iOS App.

It’s horrible.

Here is a screenshot of the regular timeline view.

What you can notice immediately is the border around content. That border might even look OK while you are looking at the screenshot as a pure image. When you put this screenshot into the realistic environment – on the iPhone – you get this:

And you can instantly see double border. One is the software drawn border in the App, and another is hardware border, so called end-of-the-screen. Seriously, there is no need to draw vertical border when the device itself creates the border. It all just looks messy and sloppy.

Here is just a quick fix of removing the border. I have not even re-positioned the elements to take advantage of extra space gained by this (about 20 pixels on each site). It looks much simpler and less visually cluttered.

The whole Twitter for iOS App is plagued with this additional vertical border between content and edge of the screen. Why? Noone knows. But this is relevant:


In conclusion, just to say it again:
Dear iOS Apps designers. The device itself is a border. Consider that when designing.

]]>
https://www.nivas.hr/blog/2011/12/09/twitter-redesigns/feed/ 0
Predictions for the future https://www.nivas.hr/blog/2011/10/28/predictions-for-the-future/ https://www.nivas.hr/blog/2011/10/28/predictions-for-the-future/#comments Fri, 28 Oct 2011 10:07:30 +0000 https://www.nivas.hr/blog/?p=2152 Here is my short list of technology predictions for the future regarding Apple.

VOICE OVER IP
iOS will have built in voice-over-IP as it has iMessages today. Today iPhone knows that it is talking to another iPhone on the other side of iMessage App. It is just a matter of small step to include this detection progress into your phone addressbook and whenever you call aonther iOS (and you have bandwidth to support it) you get a free call. Well, not free free, you still pay for data, but you get the idea. Some people argue that this would be too big kick-in-the-balls for the telecoms. However, this already happened with fixed line telecoms. We all had land line phones. Then we upgraded this to include fast internet connection, and today those fixed line telecoms serve the purpose of only ISP. Most people I know no longer use “ordinary” land lines. This will happen to mobile phone carriers as well. Their “land line” will diminish and only internet connection will remain.

APPLE TV
Apple will build a TV. In essence, it will be a large iPad. There will be App Store to hold apps for TV which can be used with remote as interface, access to iTunes and all the shows and movies there. In combination with iCloud this is ideal. You never store anything on your TV, there is no need to download, only stream. Of course, the TV needs to have all sorts of connectors on the back so you can watch ordinary stuff as well. Why TV? Simple, Apple needs to extend the reach into consumer market even further. Not everyone is OK with the way iPad works and feels. For some, it is too complicated. And TV – if well designed – means more market. It’s a no brainer.

SCREEN SIZE AND RESOLUTION
iPad will get Retina class display. But the physical size of displays in both iPod/iPhone and iPad will stay the same. People all over the internet debated why should it stay the same, and all sorts of answers arise. Most common is that on iPhone now, while you hold it with one hand you can use just your thumb to cover entire screen, there is no need to fumble your phone in hand. However, there is one more and I would say bigger reason: if you enlarge the screen while keeping the pixel count the same, things start to look more pixelated (obviously) and a lot of design tricks would no longer work. Using one pixel line on Retina class display makes that line almost invisible, yet it serves a purpose. It would be impossible for the designers to design high quality Apps when one pixel has a different thickness depending on the device. This would bring Apple into Android fragmentation world where designers have no clue how exactly their App will look like. No go.

SOFTWARE, NOT HARDWARE
Future Apple devices, primarily iPhone and iPad will make progress more through software and less through hardware. Yes, each new divce will sport faster processor, more RAM and better camera, but these are obvious upgrades. iPhone 4S has shown the obviousness of this trend: the biggest upgrade is software based; Siri. iPhone 5 will, of course, have bigger CPU, but any major breakthrough it will have will come through software. Maybe iPhone 5 will have that voice-over-IP in it? The point here is that the software will become the distinguishing detail between new devices, not hardware.

BUSINESS
Apple will sooner or later start tapping into business sector. It will be through corporate level software integration. Business-grade iCloud accounts? Integration of SIRI into business through some means (“Siri, pay invoice number 3451 which ACME inc. sent”). I am not sure how, but it will happen, and it will happen soon.

GOING BIG
Apple will extend their core business into a field which can further support their devices. Apple could branch out to be a mobile network carrier. And they would only need to use data, not regular GSM voice transfer. Simply because at this moment, the only weak link in the entire ecosystem is the carrier. From lousy support carriers give to their subscribers, to bad coverage, to occasional blackout. Most of Apple devices today are basically useless if there is no data network available, preferably high speed one. They will need to sort that out, and the only way to sort it is to go independent.

]]>
https://www.nivas.hr/blog/2011/10/28/predictions-for-the-future/feed/ 1