Full Blog TOC

Full Blog Table Of Content with Keywords Available HERE

Wednesday, August 11, 2021

Relative Variability

 

In this post we will review the relative variability, and check how can we use it to compare variance of 2 different sets.

First let's create a function that gets a set of numbers:

func stats(numbers []float64) {
// ...
}


Calculate the mean:


sum := float64(0)
for _, element := range numbers {
sum += element
}
mean := sum / float64(len(numbers))
fmt.Printf("mean: %v\n", mean)



The variance and the standard deviation:


variance := float64(0)
for _, element := range numbers {
distanceSqr := math.Pow(element-mean, 2)
variance += distanceSqr
}
variance = variance / float64(len(numbers))
std := math.Sqrt(variance)
fmt.Printf("variance: %v\nstd: %v\n", variance, std)



and lastly the relative variability:


relativeVariability := std / math.Abs(mean)
fmt.Printf("relative variability: %v\n", relativeVariability)


The relative variability can be used to compare between different sets, let examine some examples.


The basic example is a set which is a constant number - all identical.



fmt.Println("=== constant ===")
numbers := make([]float64, 1000)
for i := 0; i < len(numbers); i++ {
numbers[i] = 100
}
stats(numbers)


And the result is



=== constant ===
mean: 100
variance: 0
std: 0
relative variability: 0



Which is quite expected. But let us check two other sets, one is using random numbers in range 0-100, and the other is using random number is range 0-1000.


fmt.Println("=== random 0-100 ===")
numbers = make([]float64, 1000)
for i := 0; i < len(numbers); i++ {
numbers[i] = rand.Float64() * 100
}
stats(numbers)

fmt.Println("=== random 0-1000 ===")
numbers = make([]float64, 1000)
for i := 0; i < len(numbers); i++ {
numbers[i] = rand.Float64() * 1000
}
stats(numbers)



And the result is:

=== random 0-100 ===
mean: 50.761294848805164
variance: 855.2429996004388
std: 29.24453794472463
relative variability: 0.5761188328987829
=== random 0-1000 ===
mean: 504.7749855147492
variance: 84752.21156230739
std: 291.122330923458
relative variability: 0.5767368417168754



We can see that the relative variance of the 2 sets is almost identical. This enables us to deduct that the variance behaves in a similar way.


What about slicing different sizes of samples?
In the following, we will use different sections of the same set.



numbers = make([]float64, 100000)
for i := 0; i < len(numbers); i++ {
numbers[i] = rand.Float64() * 100
}
fmt.Println("=== random using sections - size 10 ===")
stats(numbers[:10])
fmt.Println("=== random using sections - size 100 ===")
stats(numbers[:100])
fmt.Println("=== random using sections - size 1000 ===")
stats(numbers[:1000])
fmt.Println("=== random using sections - size 10000 ===")
stats(numbers[:10000])
fmt.Println("=== random using sections - size 100000 ===")
stats(numbers)



And the result is:

=== random using sections - size 10 ===
mean: 39.58300235324653
variance: 768.6101314621283
std: 27.723818847015437
relative variability: 0.7003970694189037
=== random using sections - size 100 ===
mean: 46.59611065676921
variance: 968.7449099481023
std: 31.124667226303036
relative variability: 0.6679670639373723
=== random using sections - size 1000 ===
mean: 49.66072506547081
variance: 814.3409641880382
std: 28.536660004072626
relative variability: 0.5746323672570423
=== random using sections - size 10000 ===
mean: 49.99659036064225
variance: 833.8674856543009
std: 28.876763766985746
relative variability: 0.5775746617656908
=== random using sections - size 100000 ===
mean: 50.05194561771965
variance: 831.7444664704859
std: 28.839980347955958
relative variability: 0.5762009846375657


So we can see that above a certain size of set, we get enough accuracy for the relative variability. Looks like small set suffer from varying variance. In some runs the relative variability of the set of size 10 was very high, while in other it was very low. So to use relative variability, or actually any statistic method, make sure the set is big enough.






Wednesday, August 4, 2021

GO and Race Condition


 


A race condition is defined as:

race condition or race hazard is the condition of an electronicssoftware, or other system where the system's substantive behavior is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when one or more of the possible behaviors is undesirable.

        quoted from the Wikipedia site.


Go includes a race condition detector that is very simple to use:



$ go test -race mypkg    // test the package
$ go run -race mysrc.go  // compile and run the program
$ go build -race mycmd   // build the command
$ go install -race mypkg // install the package

        quoted from the Go lang blog.


I've used the race condition detector on my code, and found warnings about locations that surprised me. See the following code example:



var count int

func main() {
go update()
for {
fmt.Println(count)
time.Sleep(time.Second)
}
}

func update() {
for {
time.Sleep(time.Second)
count++
}
}



I got a warning about the count global variable, and the reason is that:

Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access.

        quoted from the Go lang site.


But then, I thought to myself: "I don't care if I get an outdated value; I do not need a 100% accuracy here. I only want to get an update sometime, and I don't want to spend CPU time on a synchronization mutex in such a case".


So I had posted a question in StackOverflow, and it seemed to annoy some people, but all I wanted is to understand if this is indeed a bug, or am I just going to get outdated values. And the answer I got from everyone is that this is a bug. But I could not understand why.

They claimed that the code might not just get outdated values, but it can also crash, and do anything unexpected. 


So I decided to run some tests, and finally got to this version, where I've increased the speed of the update() Go routine, and let the main() print the status once in a second.



var count int32

func main() {
go update()

lastPrinted := time.Now()
for {
now := time.Now()
if now.Sub(lastPrinted) > time.Second {
fmt.Printf("count %v\n", count)
lastPrinted = now
}
}
}

func update() {
for {
count++
}
}


Now, the output is ALWAYS:



count 0
count 0
...  
  


And now I am a true believer that the race detector reports should never be ignored...



Tuesday, July 27, 2021

IP lookup in CIDR blocks

(image taken from the cidranger site)

 

Lately we've had a new requirement in our system: whitelist IPs.

This means that whenever a new client request arrives to our service, our system should first look fo the client IP in a predefined list of IPs, and in case the IP exists, the system should allow it to pass without any validations.

This sounded quite simple, until I've realized that the whitelist IPs is not a list of IPs, but a list of CIDRs, for example: 

1.2.3.0/24

While I could create a new code to scan the whitelist, and check inclusion within any subnet, it would had been very inefficient, especially considering the whitelist is ~1K long.

The solution for this is to represent the entire whitelist as a Trie, and then simply look for the IP in the trie.

Building the Trie is O(N) where N is the length of the whitelist, while searching the IP in the entire whitelist Trie, is O(number of bits in a IP) ~= O(1).

Luckily, I am not the first one who needed this, and I've found the cidranger GO library.


The following is an example of building the Trie:



cidrs := []string{"1.2.3.0/24", "1.1.1.1", "1.1.1.2/32"}

ranger := cidranger.NewPCTrieRanger()
for _, cidr := range cidrs {
if !strings.Contains(cidr, "/") {
if strings.Contains(cidr, ":") {
cidr += "/128"
} else {
cidr += "/32"
}
}

_, parsedCidr, err := net.ParseCIDR(cidr)
if err != nil {
panic(err)
}

err = ranger.Insert(cidranger.NewBasicRangerEntry(*parsedCidr))
if err != nil {
panic(err)
}
}



To lookup in IP, use the following code:



parsedIp := net.ParseIP("1.2.3.7")
included, err := ranger.Contains(parsedIp)
if err != nil {
panic(err)
}

fmt.Printf("IP included: %v",included)



It worth mentioning the library supports both IPv4 and IPv6.




Wednesday, July 21, 2021

Disk Space issues in Ubuntu VM

 


In this post we will review how to overcome disk space issues in an Ubuntu VM.

This week, we've had our build machine out of disk space due to docker images disk requirements. This occurred since we keep the latest version per each git branch. Even after cleanup of some old images, we realized that we should have more disk space available on the build machine.

The steps to add disk space are listed below.


Add New Disk to the VM

Use the VM management tools to add new disk to the VM.
Notice: reboot the machine after this, to make the OS identify the new added disk.

Create a Partition and a FileSystem

Identify the new added disk using the command: sudo lshw -C disk

An example of output is:



In this case the new disk device is /dev/sdb, which is a shorthand for Scsi Disk B.

To create a partition, run sudo cgdisk /dev/sdb, where the argument is the new disk device. 

  • In the cgdisk utility, create a new partition. 
  • Use the defaults values for the partition creation.
  • Make sure to select "Write" option after the partition creation.

Next we create a filesystem using the command: sudo mkfs -t ext4 /dev/sdb1
Find the UUID of the new filesystem using: sudo blkid | grep /dev/sdb1.
For example:



Copy the UUID, and edit the fstab: sudo vi /etc/fstab.

Duplicate one of the UUID line, and replace the UUID with the one created in the previous step. Also select an existing folder to mount the disk on. In our case we use a new folder for the docker images, hence we use the folder /var/lib/docker/overlay2.




To check that this is working, use the command: sudo mount -av.

Finally reboot the machine to verify that everything works.






Open the Default Mail Client from JavaScript


 


In this post we will review how to open the default mail client on an end user machine using javascript from a site. We want the email to be created in a new window, so the current site does not disappear after sending the email. This is just a small effort, but so unclear, that it worth mentioning.



const encodedSubject = encodeURIComponent('My Subject')

const encodedBody = encodeURIComponent(`The email body.
New
lines
can
be
included
here
`)

const link = `mailto:you@company.com?subject=${encodedSubject}&body=${encodedBody}`
window.open(text, '_blank')



Wednesday, July 7, 2021

Break the Glass




 

This post is another small change I've made as part of lesson learnt from the Google SRE book.

See this post for more details.


Some of our produce management functions are dangerous for the production environment. For example, we have several buttons to reset the system state in case of a non-recoverable problem. For example, one of these buttons will reset the production redis cluster.

These buttons a crucial in case of an emergency situation, but in case of a normal system operation might cause unnecessary damage. I've decided to use the SRE's "Break the glass" method for these buttons. By using it, i mean literally...


So I've created a small react component to protect the emergency buttons. This component can wrap any set of react children. It protects the components by hiding them behind a glass, which is broken only when the user clicks multiple times on the items.


glass.js

import React, {useState} from 'react'
import {Behind, GlassCover, Root} from './style'

function Glass(props) {
const {children} = props
const [count, setCount] = useState(0)
const max = 4

function clickHandler() {
if (count === max) {
alert('The Glass is Broken')
}

setCount(count + 1)
}


let zIndex = 10
if (count > max) {
zIndex = -1
}

return (
<Root>
<GlassCover
opacity={count / max}
zIndex={zIndex}
src={process.env.PUBLIC_URL + '/glass.png'}
onClick={clickHandler}
>

</GlassCover>
<Behind>
{children}
</Behind>
</Root>
)
}

export default Glass


We have the styled components CSS as well:


style.js

import styled from 'styled-components'

export const Root = styled.div`
position: relative;
`


export const GlassCover = styled.img`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
${({zIndex}) => `
z-index: ${zIndex};
`}
${({opacity}) => `
opacity: ${opacity};
`}
`

export const Behind = styled.div`
`


And the image, is the same image which appears in the post header.

Feel free to use...


Wednesday, June 30, 2021

Using WebPack to Create Separate Distribution for Internet Explorer


 


In this post we will use webpack to create two distributions. The first would be for Internet Explorer, and the second would be for all other browsers. This would enable us to reduce the size of the distribution for most of the end users, and supply a compatible distribution for IE users.


First we need to update the build to run the webpack twice, once for the default users, and once for IE users.


package.json

...
"scripts": {
"build": "webpack --config webpack.config.default.js && webpack --config webpack.config.ie.js",
...



The IE webpack includes the following:


webpack.config.ie.js

const config = {


... // skipping non relevant configuration

entry: {
index: ['core-js/stable', path.resolve(__dirname, './index.js')],
},
output: {
path: path.resolve(__dirname, 'build/ie/'),
},
}


config.module.rules[1].use.options.presets = [
[
'@babel/preset-env',
{
'debug': false,
'targets': {
'ie': '11',
},
'useBuiltIns': 'usage',
'corejs': {
'version': 3,
},
},
],
]


For more details of IE transpilation, see this post.



The default webpack includes:


webpack.config.default.js

const config = {

... // skipping non relevant configuration

entry: {
index: [path.resolve(__dirname, './index.js')],
},
output: {
path: path.resolve(__dirname, 'build/default'),
},
}

config.module.rules[1].use.options.presets = ['@babel/preset-env']


Now, running the build process creates two folders: build/ie for the IE distribution, and build/default for all of the other browsers.


In case using NGINX as a web server for the distribution, it can be configured to use the relevant distribution folder, see this post for details.