Tuesday, 27 August 2024

SGActive Plus: How much of a plus is it?

Booking sports facilities in Singapore is undergoing an upgrade... of sorts. I found this out earlier in the month when, on my daily evening swim at Clementi Swimming Complex, I was alerted to a poster by the entrance. This poster informed of an upcoming upgrade to the existing SGActive app on the 15th of August.

Informational posters.

On the 15th of August, the existing app would no longer be in use. All swim passes would need to be purchased on the new SGActive Plus web portal. Logging in to the new SGActive Plus web portal before the end of this month, would also get you 10 free credits. I wasted no time logging in because, being a regular user of the facilities, I needed to ensure that there would be minimal interruption to my lifestyle.

What happened

On the 13th of August, my current monthly pass expired and I had to purchase a new one. Due to the fact that it was not yet the 15th, I had to purchase my monthly swim pass from the old platform, with the understanding that this pass should still last an entire month. However, when I went to Clementi Swimming Complex again on the 15th of August, the gantries at the entrance had been blocked and now we needed new passes that had to be purchased from the new system.

No longer accessible.

The elderly attendant at the entrance cheerfully helped me navigate to the web portal and used one of my 10 free credits to purchase a pass for the day, assuring me that whatever I paid for my monthly pass previously, would be refunded in September. That was when I paused - if I had to do this every day till September arrived, the free credits would run out in no time. Surely this attendant should have helped me purchase a monthly pass (now known as a multi pass) instead? But no, he'd made an assumption. And now that I only had 9 credits left, attempting to purchase a 10 GSD monthly pass on the new platform would cost me a dollar. Not that I cared about the dollar - it was more the troublesome process of paying online. A process I could have avoided if this attendant had just thought to ask me instead of simply assuming a single pass was what I needed.

But that's OK. Didn't really want to get on this old man's case too much. It's not like he asked for any of this. I'm sure this was a great cushy retirement gig up to now.

Is this an improvement?

Well, in terms of the interface used to make purchases, I'm of the opinion that it would be extremely difficult to have any interface be worse than the one employed by the SGActive app. My feelings on this absolute abortion of an app have been made abundantly clear in my last review in 2018.

In terms of having to present an animated pass on my phone instead of simply having a reader at the gantry scan my Identity Card, I suppose one could argue that it's more secure. To use an Identity Card, one only needs to be in possession of said Identity Card. To use the pass displayed on the phone, one needs to be in possession of the phone and have the credentials (usually biometrics) necessary to log into the phone and access the SGActive Plus web account.

Animated
swim pass.


However, I'd also venture to say that in the event that a malicious actor came into possession of someone's Identity card, using it to obtain access to sports facilities wouldn't exactly be high on the list of things to do.

All said and done, I'm in favor of any proposed solution that will allow me to speak of the SGActive app in the past tense.

The real issue

So, the change was a good thing overall. The isssue I take with this, is the implementation of the change. I'm pretty sure I wouldn't have been the only one to be caught off-balance by the sudden nature of the cut-off.

One day we're using this platform, and a day later, we have to use something else. In most cases of system upgrades, old and new systems are run in parallel to minimize disruption to users while preparing them for the inevitable. Plus, with more time to prepare, the attendant would (hopefully) have done a better job. There'd be less confusion, for sure.

The new
10 SGD
monthly
swim pass.


The 10 free credits was a good idea, both as an incentive for early adoption, and a good way to ease users over to the new platform. Unfortunately, cases like mine happened; and I wonder if 12, maybe 15 free credits would have been better if we absolutely had to do that sudden cut-off. At the very least, we could have used those credits to figure the system out. Make some mistakes.

As it was, whatever money I spent on the old platform that couldn't be moved to the new, would be refunded next month. At the end of next month. It's fine, it's 10 bucks. Still annoying, but not a show stopper.

Final thoughts

Not to be too critical of change; it was about time. Though one might argue that the time for a new improved platform was roughly ten years ago when the app first came out; it was that bad.

Hope your day's going swimmingly well,
T___T

Thursday, 22 August 2024

Singapore's Smoking Samsui Woman Controversy Through a Web Developer's Eyes

The story I'm about to talk about today, is pretty old, at least in the news cycle context. It happened more than a month back, and the incident is fast fading from public consciousness. It's taken me at least that long to come to terms with it, and understand why certain elements of the case offended me on such a profound level.

To anyone who doesn't want to beat this particular dead horse, I more than understand; go play with another ball of wool and watch some funny cat videos while I unpack this.

What happened

The owner of a building in Chinatown had hired an artist, one Sean Dunston, to paint a mural on the exterior wall. Dunston decided to paint a tribute to some of the pioneers of Singapore, the Samsui woman. To that end, he produced a work covering the wall, of a young, slender woman in the blue work clothes and red hat of the quintessential Samsui uniform, smoking a cigarette.

From Google Maps

The Urban Redevelopment Authority (URA) issued an order to the owner of the building to remove the cigarette from the mural, as the depiction of smoking on a public-facing wall contravened the Singapore Government's stand on advertisements for tobacco products and smoking.

The URA also cited feedback form an unnamed member of the public. In Sean Dunston's original Instagram post, this was an excerpt from their correspondence. 
We wish to bring to your attention that URA has received feedback on the mural from a member of public. The feedback received is as follows - "We find this mural offensive and is disrespectful to our samsui women. The woman depicted in this mural looks more like a prostitute than a hardworking samsui woman."

Cringe, man. Massive cringe. More on that later.

The URA then followed up by saying that the mural would have to be changed, or the restaurant which operated in this building would have its temporary license revoked.

Reactions

It was a heady mixture of scorn and disbelief that anyone could mistake a simple picture of a female laborer sitting down and enjoying a cigarette as the come-hither soliciting pose of a prostitute. There were those who opined that the cigarette, while possibly too modern (in those times, it was more likely to be of the hand-rolled variety), was absolutely a feasible historical representation of the quintessential Samsui woman.

No artistic freedom?

There were those who expressed concerns about artistic freedom in Singaporean society.

And yes, those who actually thought that the Samsui woman absolutely did look like a prostitute. This weirdness thankfully seemed to be at a minimum, though I have suspicions that these were basically just civil servants in subtle damage control mode trying to make the URA's instruction seem reasonable.

The Association of Women For Action And Research (AWARE), perhaps feeling the need to appear relevant, also weighed in with some garbage about perpetuating "a male gaze", and then backtracked later. 

What really offended me, and why

Now, the actual opinion of the Samsui woman looking like a prostitute, though I'm thoroughly dumbfounded by that, wasn't by itself offensive. I'm not offended by the fact that people have opinions, and that often these opinions don't match mine. That's a fact of life, and, where subjective topics such as art and morality are concerned, par for the course.

What did offend me was that the civil servant who drafted the URA's response, saw fit to include the feedback of that one member of the public, and acted as though this inclusion was ample justification for their decision. Who was this one anonymous person whose opinion held so much weight? Was it their own opinion which they then passed off as some fictional other person's? Was it their mother-in-law? Some random Karen passer-by? Our newly minted Prime Minister Lawrence Wong? Our President, Tharman Shanmugaratnam? Which individual, out of Singapore's population of over 5 million, has enough clout to speak for all of us on this matter? We'll never know, because the URA also decided not to disclose their identity.

Our mysterious art critic.

The fact that the URA gave such a nonsensical reason as justification, is insulting; even more insulting than if they had not deigned to give a reason at all. It smacked of the good old heavy-handed, "I have the authority to do this, just take it and be grateful I even bothered to give you a reason, peasants."

This irks me due to similar experiences I've had in web development. Clients somehow seem to think that the fact that they pay for a website entitles them to say things that don't make any kind of logical sense. Buddy, you paid for a website. You didn't pay for the ability to redefine reality.

"This color scheme makes me feel moody. This needs to be more vibrant." It's been decades, but I don't think the technology to create a website based on subjective feelings, exists in any part of the world.

"I don't like this. Let's redo it." Pretty sure that in order to get anywhere, we'll need something a lot more concrete than "I don't like it". But for some mystifying reason, people confuse web developers with mind readers.

"I asked my friend what he thought and he didn't like the layout." Again, who is this "friend"? The imaginary childhood kind? Or at least some authority in the field of web design? Also, doesn't it occur to them that the website isn't actually meant for them (or their friends), but their target audience?

The URA response was a great example of the kind of out-of-touch correspondence that reeked of arrogance. The sort of arrogance that genuinely expects not to be ridiculed for providing such a laughable response. The sort of arrogance that comes with having precious little experience in justifying oneself, or worse, offering something rather more convincing than "because I said so". The sort of arrogance so cartoonish that it borders on comedy.

The similarities of this case to my previous experiences as a web developer - vague subjective objections, feedback from anonymous unrelated person cited and all - were pretty triggering by themselves. But at the same time, I fervently hope that this isn't the quality we can expect of our civil servants.

Many Asians have older relatives that expect to be taken seriously (even when they talk nonsense) simply because they're older, dammit. The URA, similarly, seems to be afflicted with the kind of characters that expect to be taken seriously (even when they talk nonsense) simply because they're the URA, dammit

So yes, I'm dismayed at the URA's response. I'm also annoyed that it took Dunston raising a stink on Instagram and the subsequent public outcry, for the URA to get their collective heads out of their asses. 

Epilogue

The mural of the Samsui Woman was allowed to remain unchanged; however the owner of the building was fined a modest 2000 SGD for contravening the regulations. This seems like a good compromise

And hopefully, the URA has learned a PR lesson from this; or better still, properly educated the civil servant in question. Singapore can do better; indeed, if we want to continue punching above our weight, we need to.

Your moural authority,
T___T

Saturday, 17 August 2024

Web Tutorial: Python Matplotlib Heatmap (Part 2/2)

Here comes the fun - or at least, the visual - part. We work on the heatMap() function.

We start off by setting the size of the plot.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))


Then we feed in the values, vals, to the lmshow() method. It's a two-dimensional list, remember.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals)


Finish off with a call to the show() method, which displays the heatmap.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals)

  plt.show()


This is the default view. You can't make much sense of it just yet, because the labels aren't clear.


Now, use the xticks() method. If we use numpy's arange() method and pass in 0 and the length of players, we'll get an array of values from 0 to the length of players, minus 1. Pass this into the call to xticks(). This will change nothing, because that's already the default setting.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals)
  plt.xticks(np.arange(0, len(players)))

  plt.show()


But if you pass in players as an argument after that...
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals)
  plt.xticks(np.arange(0, len(players)), players)

  plt.show()


...you see the x-axis is populated with values! However, the names can be a little long (looking right at you, Alex Oxlade-Chamberlain!) so let's modify the code a bit.


Add a rotation value as an argument.
plt.xticks(np.arange(0, len(players)), players, rotation=90)


Looking good!


We do something similar for the yticks() method, passing in seasons for the labels.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals)
  plt.xticks(np.arange(0, len(players)), players, rotation=90)
  plt.yticks(np.arange(0, len(seasons)), seasons)

  plt.show()


And now our heatmap takes shape. We have labels, now we just need to know what those colors mean.


Let's use the colorbar() method to facilitate this...
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals)
  plt.xticks(np.arange(0, len(players)), players, rotation=90)
  plt.yticks(np.arange(0, len(seasons)), seasons)

  plt.colorbar()

  plt.show()


Now you can see, with the bar on the right appearing, that yellow is a higher number and deep blue is 0.


Let's go for a better color palette. It's Liverpool, so pass in "Reds" as the cmap argument.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals, cmap="Reds")
  plt.xticks(np.arange(0, len(players)), players, rotation=90)
  plt.yticks(np.arange(0, len(seasons)), seasons)

  plt.colorbar()

  plt.show()


Now you can see the color scheme has changed, along with the color bar!


Let's do something simple and add a title using the title() method and stat parameter value.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals, cmap="Reds")
  plt.xticks(np.arange(0, len(players)), players, rotation=90)
  plt.yticks(np.arange(0, len(seasons)), seasons)

  plt.colorbar()

  plt.title("Liverpool FC Player " + stat)
  plt.show()


There it is... a small piece of the puzzle but adds so much clarity.


Next, we will add labels to the colored squares. It's quite straightforward - first we need a nested For loop top traverse through the two-dimensional list that is vals. For both the inner and outer For loop, we need the index value, thus we will have to use enumerate(), first on vals, then on each list within vals.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals, cmap="Reds")
  plt.xticks(np.arange(0, len(players)), players, rotation=90)
  plt.yticks(np.arange(0, len(seasons)), seasons)

  plt.colorbar()
  
  for colindex, lst in enumerate(vals):
    for rowindex, val in enumerate(lst):


  plt.title("Liverpool FC Player " + stat)
  plt.show()


Then in the inner loop, we use the index values (rowindex and colindex) and the value itself, val, in the text() method.
for colindex, lst in enumerate(vals):
  for rowindex, val in enumerate(lst):
    plt.text(rowindex, colindex, val)



Pretty straightforward.... though you may have noticed that the default color is black and that Mohamed Salah's goal talley for the 2017/2018 season here is almost invisible due to the lack of color contrast.


Let's fix that! Just before the nested For loop, declare vals_avg and set the value using the nanmean() method, passing in vals as an argument. Thus we obtain the average value of the entire dataset in the heatmap.
def heatMap(seasons, players, vals, stat):
  plt.figure(figsize = (10, 10))

  plt.imshow(vals, cmap="Reds")
  plt.xticks(np.arange(0, len(players)), players, rotation=90)
  plt.yticks(np.arange(0, len(seasons)), seasons)

  plt.colorbar()
  
  vals_avg = np.nanmean(vals)
  for colindex, lst in enumerate(vals):
    for rowindex, val in enumerate(lst):
      plt.text(rowindex, colindex, val)

  plt.title("Liverpool FC Player " + stat)
  plt.show()


Then in the inner For loop, declare rgb as 0. If the value of val is greater than vals_avg, set it to 1. So now we have a situation where, if val is below average, rgb is 0, and otherwise it is 1.
vals_avg = np.nanmean(vals)
for colindex, lst in enumerate(vals):
  for rowindex, val in enumerate(lst):
    rgb = 0
    if (val > vals_avg): rgb = 1

    plt.text(rowindex, colindex, val)


And then we modify the call to the text() method to include a color argument.
vals_avg = np.nanmean(vals)
for colindex, lst in enumerate(vals):
  for rowindex, val in enumerate(lst):
    rgb = 0
    if (val > vals_avg): rgb = 1
    plt.text(rowindex, colindex, val, color=())


And we will use the value of rgb in that argument! This means that we will have white if val is above average, and black if below.
vals_avg = np.nanmean(vals)
for colindex, lst in enumerate(vals):
  for rowindex, val in enumerate(lst):
    rgb = 0
    if (val > vals_avg): rgb = 1
    plt.text(rowindex, colindex, val, color=(rgb, rgb, rgb))


See what we've done? Now in cases where the square color is dark (which means the value is above average), the text is white. And in cases where the square color is white or light (which means the value is below average), the text is black!


Well, this was fun...

Heatmaps are a nice visualization tool provided you have a somewhat even spread of number of possible values along two dimensions.

Warm regards,
T___T

Wednesday, 14 August 2024

Web Tutorial: Python Matplotlib Heatmap (Part 1/2)

How's everyone doing?

After all we've been through with the Liverpool Football Club dataset, here's one more!

Today we will get to making a heatmap in Python's matplotlib library. It's rather like a line chart in the sense that we plot data over two axes. The difference is that the data will be a two-dimensional list. Thus, while the data gathering is very straightforward - we ask the user if they want to see goals or appearances, and that's it! - the data massaging is a little more complex.

Let's start by taking the code for the line chart, and renaming the lineChart() function to heatMap(). Instead of labels as an argument, we will have two arguments in place of labels - seasons and players. Clear everything else except for the seasonName() function; we will still need that. Needless to say, we'll need the data as well.
import numpy as np
import matplotlib.pyplot as plt

def heatMap(seasons, players, vals, stat):
  
def seasonName(year):
  return str(year) + "/" + str(year + 1)

data = {
  2017: {
    "Mohamed Salah": {"goals": 44, "appearances": 52},
    "Roberto Firminho": {"goals": 27, "appearances": 54},
    "Sadio Mane": {"goals": 20, "appearances": 44},
    "Alex Oxlade-Chamberlain": {"goals": 5, "appearances": 42}
  },
  2018: {
    "Mohamed Salah": {"goals": 27, "appearances": 52},
    "Roberto Firminho": {"goals": 16, "appearances": 48},
    "Sadio Mane": {"goals": 26, "appearances": 50},
    "Alex Oxlade-Chamberlain": {"goals": 0, "appearances": 2}
  },


After the data portion, clear everything; we'll start relatively fresh. Declare two lists - players and seasons. These will be used as arguments in the heatMap() function later.
  2022: {
    "Mohamed Salah": {"goals": 30, "appearances": 51},
    "Roberto Firminho": {"goals": 13, "appearances": 35},
    "Alex Oxlade-Chamberlain": {"goals": 1, "appearances": 13},
    "Diogo Jota": {"goals": 7, "appearances": 28},
    "Luis Diaz": {"goals": 5, "appearances": 21}
  }
}

players = []
seasons = []


Use a For loop. For every element in data, use seasonName() to derive the season name and then append to seasons. At the conclusion of the loop, you should have a full list of seasons.
players = []
seasons = []

for season in data:
  seasons.append(seasonName(season))


Also, derive a list of players using the list() function and using the keys() method on the current element of data. It's possible for us to just add the entire list to players using the addition operator.
for season in data:
  seasons.append(seasonName(season))
  players = players + list(data[season].keys())


Now we'll have a full list of players as well in players, but many of them will be repeats. What we'll do is use the fromkeys() method of the Python's dict object to convert list into a dictionary (removing all duplicates in the process), then use the list() function to convert back to a list. And finally, we'll sort players using the sort() method.
for season in data:
  seasons.append(seasonName(season))
  players = players + list(data[season].keys())
  
players = list(dict.fromkeys(players))
players.sort()


We follow up by declaring ans as an empty string, then creating a While loop that runs as long as ans is not 0.
players = list(dict.fromkeys(players))
players.sort()
  
ans = ""

while (ans != 0):


In the loop, print the options - "1" for goals, "2" for appearances, and "0" to exit.
ans = ""

while (ans != 0):
  print ("1: goals")
  print ("2: appearances")
  print ("0: Exit")


In here, create an infinite While loop.
while (ans != 0):
  print ("1: goals")
  print ("2: appearances")
  print ("0: Exit")

  while True:


Inside this While loop, we have a Try-catch block.
while True:
  try:

  except:


Here, we'll use the input() function to ask the user for input, then use the int() function to convert the input to an integer. If the user enters a letter instead, this will trigger an exception, which we will then handle with the print() function.
while True:
  try:
    int(input("Select a stat"))
  except:
    print("Invalid option. Please try again.")


Now, assign the value of the input to ans, and then use a break statement. This means that if an exception is not triggered by an invalid input, the user will break out of the infinite loop.
while True:
  try:
    ans = int(input("Select a stat"))
    break
  except:
    print("Invalid option. Please try again.")


Then, depending on the value of ans, assign a value to stat. But if ans is 0, we stop all execution. If ans is not 0, 1 or 2, we restart the loop.
while (ans != 0):
  print ("1: goals")
  print ("2: appearances")
  print ("0: Exit")

  while True:
    try:
      ans = int(input("Select a stat"))
      break
    except:
      print("Invalid option. Please try again.")
  
  if (ans == 1): stat = "goals"
  if (ans == 2): stat = "appearances"
  if (ans == 0): break
  if (ans > 2 or ans < 0): continue


As you can see, I entered "hello" and it was rejected, then I entered "5", and "-1", both of which resulted in being asked the question again. It was only after I entered a "0" that the script terminated.


We will need a two-dimensional list, stats. And then we're going to populate stats using a nested For loop. We first iterate through the seasons. Now we can't use the list seasons for this because it contains the proper season names instead of the keys in the dataset. So we'll need to use a list of the keys in data. In the inner loop, we iterate through players.
if (ans == 1): stat = "goals"
if (ans == 2): stat = "appearances"
if (ans == 0): break
if (ans > 2 or ans < 0): continue
  
stats = []

for season in list(data.keys()):
  for player in players:


In the outer loop, we declare an empty list, arr. Then in the inner loop, we use an If block to check if player is in the element of data pointed to by season.
stats = []

for season in list(data.keys()):
  arr = []
  for player in players:
    if (player in data[season]):

    else:


If so, we append the relevant stat to arr. If not, we simply append a zero.
for season in list(data.keys()):
  arr = []
  for player in players:
    if (player in data[season]):
      arr.append(data[season][player][stat])
    else:
      arr.append(0)      


And finally, in the outer loop, we append arr to stats. By the end of this, stats should be a two-dimensional list of stats for each player by season, with 0s where the player was not present during the season (or simply made no appearances or scored no goals).
for season in list(data.keys()):
  arr = []
  for player in players:
    if (player in data[season]):
      arr.append(data[season][player][stat])
    else:
      arr.append(0)
      
  stats.append(arr)


And then we call the heatMap() function, passing in seasons and players for the labels, then stats as the values, and stat as the string variable that we'll use in the title.
for season in list(data.keys()):
  arr = []
  for player in players:
    if (player in data[season]):
      arr.append(data[season][player][stat])
    else:
      arr.append(0)
      
  stats.append(arr)

heatMap(seasons, players, stats, stat)


Next

We implement a heatmap using the data we have obtained!

Thursday, 8 August 2024

Software Review: XAMPP

My first experience in 2010 configuring PHP on a Windows laptop was a bit of a nightmare. I not only had to run the executable file, I had to grapple with every line from php.ini. And that's not to mention running code and then testing database connections.

So image my relief years later when I discovered XAMPP, a package that would set up an Apache server, just like that.



After all the steps I'd taken to install PHP on that first laptop, not having to do it all over again, over and over, on the subsequent machines that I owned, was palpable. I soon became a big fan.

And today, I'm going to show my appreciation through this software review.

The Premise

XAMPP is basically a package comprising of an Apache server, and optionally, MySQL (recent versions use MariaDB instead). Thus if you need to run PHP or Perl quickly, this is a great solution. In fact, "XAMPP" is an acronym that stands for "Cross-platform Apache MySQL PHP Perl".

You double-click to run the installer. Subsequently, whenever you want to run PHP or Perl code, you start up the server.

The Aesthetics

Meh, it's orange and grey. The whole thing looks basic. Nothing to shout about from an artistic viewpoint, really. But at the same time, there's something about the simplicity of it all that's really attractive.

The Experience

Overall, using XAMPP was a pleasure. Compare this to the hassle of setting up and maintaining your own Apache server? Not even close. Come on.

The Interface

Starting this thing up is easy. Configuring it, also easy.





Gone are the days of hunting for the exact file. XAMPP opens that file up for you and gently warms you to be careful when changing it.

What I liked

XAMPP condenses the horribly complicated process of setting up an Apache server with a database, regardless of whether it's on a Windows or Mac platform, into a few simple steps. What's not to love? It's almost a bit too simple, if I'm being honest. But too simple is usually better than not simple enough.

Sensible conventions. Things like the default deployment path, port number, and such, aren't outlandish. I can get behind "htdocs" as a root path, even if I've been trained to recognize other conventions such as ASP.NET's "wwwroot".

Looks charmingly retro. Now, this could be seen as a negative, but this section is titled "what I liked", so here it is.

What I didn't

It would've been nice if some configurables were changeable through either a desktop or web interface rather than having to open up the config text file. On the other hand, the need to change these things doesn't come up all that often, so...

In the MacOS version, the executable is manager-osx which isn't really intuitive. 



Conclusion

If you need a quick-and-dirty Apache setup tool, who you gonna call? That's right - XAMPP! This tool has been around for the last  couple decades, and hasn't ever really gone out of fashion. That's because XAMPP doesn't pretend to be anything more than just an Apache server setup tool, and in that it's a godsend for PHP and Perl devs.

My Rating

8 / 10

An XAMPP-lary piece of work!
T___T

Saturday, 3 August 2024

The day Microsoft stood still for CrowdStrike

It's unclear exactly when the term "BSOD" entered the public consciousness. More than a decade ago when I was working as desktop support, I knew the term to mean "Blue Screen Of Death", alluding to Microsoft's infamous default screen which warns users of a system crash. Thus, it was a little strange to see the term "BSOD" being bandied around the last couple week.


Suffice to say, the term "BSOD" has found its way into several conversations over the course of the past few weeks as Microsoft systems around the world crashed in a cacophony of azure-colored screens. The outage was caused by cybersecurity vendor CrowdStrike, as a consequence of their security program Falcon pushing a faulty update to the Microsoft branch. Once the update was downloaded and installed by roughly 8 million machines worldwide, it resulted in those systems crashing.

The fix, according to CrowdStrike, was to reboot the machines in Safe Mode, remove the file, and then reboot the machines in Normal Mode. Nothing fancy if you've ever worked in desktop support, but perhaps a little out of the grasp of the average end-user. The desktop support personnel of these affected companies would have been on high alert especially during the initial weekend. 

Damage

It's all very well to say "8 million machines crashed", but it would be far more illuminating to know ˆ those machines were doing and ˆ they were deployed. For the most part, home users who had no compelling reason to install CrowdStrike's Falcon program, were spared the outage. It should also be noted that China, having long moved on from the days where a significant portion of the country was using Windows, was unaffected (to my knowledge thus far) by this outage.

For significantly larger corporations in USA and Canada, large swathes of Europe, and India; however, this was a different story.  Airline queues were jam-packed as systems went down. Healthcare and emergency services were similarly impacted as hospital IT systems using Microsoft 365 crashed. And banks - easily the biggest item on the list. Minutes of lost service could translate to millions of dollars in transactions gone, potentially, and in terms of these outages, we're talking days

This was seen a lot.

The irony here, of course, is that CrowdStrike was employed by these companies for the express purpose of guarding against malicious attacks much like this outage. Though this is one case where we should attribute the cause to incompetence rather than malice.

CrowdStrike's error was a null pointer exception, if the Internet is to be believed. These are fairly common; not really high-level stuff. From testing procedures, deployment practices and just good old due diligence, the number of potential shortfalls in any of these areas could have resulted in the faulty update being pushed.

Silly comments

Social Media has been rife with glee from Apple fans gloating that we should all stop using Microsoft products because this was the greatest outage in history, and that Apple has never been affected to this scale. That's ridiculous when you consider that the conditions that led to these outages - cybersecurity threats, third-party cybersecurity vendors, faulty software updates - would be present in all modern operating systems regardless.

Use Apple products
instead!

Covert to MacOS to avoid outages? Absurd.

Such deranged comments are ultimately the province of mere fanboys allowing emotion rather than logic to guide the words that come out of their mouths. I'm inclined to be forgiving here; I'd be less understanding if those words came from actual tech professionals.

Finally...

It's early days yet. The fallout is still being felt around the world, though much of the initial damage has been, hopefully, resolved. Still, this is not something we'll all be forgetting anytime soon.

Over and outage,
T___T