Coding

Ryzen Ubuntu Server: Throttle CPU Frequency for power saving

This is a super quick one, I have a Ryzen server running a bunch of VMs. I noticed it’s running quite hot and pulling a fair bit of power.

As none of the VMs running are particularly performance sensitive I wanted to force the CPU to use a more conservative power setting.

First up, how do I see what frequencies the CPU is currently running at? For that we’ll use cpufreq-info, this will tell us what frequency each core is at:

cpufreq-info | grep current
  current policy: frequency should be within 2.20 GHz and 3.60 GHz.
  current CPU frequency is 4.23 GHz.
  current policy: frequency should be within 2.20 GHz and 3.60 GHz.
  current CPU frequency is 2.70 GHz.
.... more

Now how do I see what power draw this is causing, well that’s more complicated. In my case I have an APC UPS connected to the system to keep it up during power outages. This has a tool called apcaccess which gives me information about the UPS’s load. Knowing the size of the UPS you can back track from the load % to a rough watt’s usage.

In our case what I want to do though is use this to prove that changing the CPU has worked. Before making any changes it outputs 19% load.

apcaccess | grep LOADLOADPCT  : 19.0 Percent

To throttle the CPU we can use a govenor lets see what governors we have available

cpufreq-info | grep governors
  available cpufreq governors: conservative, ondemand, userspace, powersave, performance, schedutil
  available cpufreq governors: conservative, ondemand, userspace, powersave, performance, schedutil
  available cpufreq governors: conservative, ondemand, userspace, powersave, performance, schedutil
  available cpufreq governors: conservative, ondemand, userspace, powersave, performance, schedutil

Cool, well powersave looks like a good one to try out, lets give that a go by running sudo cpupower frequency-set --governor powersave and then looking at the load and frequencies again.

lawrencegripper@libvirt:~$ sudo cpupower frequency-set --governor powersave
Setting cpu: 0
Setting cpu: 1
Setting cpu: 2
....
Setting cpu: 15
lawrencegripper@libvirt:~$ cpufreq-info | grep current
  current policy: frequency should be within 2.20 GHz and 2.20 GHz.
  current CPU frequency is 2.20 GHz.
  current policy: frequency should be within 2.20 GHz and 3.60 GHz.
  current CPU frequency is 2.20 GHz.
  current policy: frequency should be within 2.20 GHz and 3.60 GHz.
  current CPU frequency is 2.20 GHz.
....
lawrencegripper@libvirt:~$ apcaccess | grep LOAD
LOADPCT  : 15.0 Percent

That did the trick, apcaccess is reporting a drop to 15% load and CPU frequency is down to 2.2GHz.

I’ve got a smart meter for my home and can also see the drop in usage roughly reflected there too.

[Edit:] I also found this isn’t persisted between reboots. The following shows how to persist the change https://askubuntu.com/questions/410860/how-to-permanently-set-cpu-power-management-to-the-powersave-governor

Done. Server is now being more eco-friendly.

Standard
Coding

Ruby + Sorbet: Autogen sig method annotations

I’ve been adding Sorbet and type checking gradually to a legacy Ruby codebase dating back to the 2010‘s.

First up was getting all or most files as (a topic for another day):

# typed: true

Now I’m gradually adding method annotations, using sig annotations, to tell Sorbet what types a method accepts and returns, like so:

sig {params(x: SomeType, y: SomeOtherType).returns(MyReturnType)} def foo(x, y); ...; end

Docs: https://sorbet.org/docs/sigs

This is fairly time consuming so being lazy I wanted some help to get this done quicker.

I say “help” here as it’s never going to be perfect, with the meta programming and cruft of an old Ruby codebase it’s always going to need human validation and tweaking.

Luckily the codebase has a pretty extensive test suite so we can use that to validate the generated types match reality.

Let’s get Autogenerating

I’m lucky to work with great engineers, George (https://hachyderm.io/@georgebrock) is one of them. He showed me a useful approach to generate sigs which saves a bunch of time.

As sorbet “knows” some return types already it can infer some sigs on methods. The trick George showed me was to get it to add those inferred sigs for you automagically.

  • Set #typed: strict on the file, this means any methods without sigs are considered errors
  • Run srb tc --autocorrect --isolate-error-code=7017 this will tell sorbet to auto create any signatures it can work out
  • Reset #typed: true (unless you’ve solved all errors under strict – I’m aiming for that but gradually, currently want good sigs)
  • Review the auto generated sigs and make sure they’re sensible, fixing up ‘untyped’ and other issues

The cool thing here is the more sigs you have the better sorbet gets at generating the missing ones.

So what about those that can’t be created with this technique?

Well you have to write them but here there is help too. I’ve being using GitHub Copilot (https://github.com/features/copilot/) to infer or suggest sigs.

This is much more hit and miss than the first technique, it still (mostly) is a time saver but you do have to tweak the suggestions regularly.

Playing it safe with new sigs

Now I’ve got a set of new sigs you’d think next up would be to ship them but hold fire.

Sig annotations are statically checked at dev time but also, by default, enforced at runtime too.

The danger here is the new shiny sigs you added aren’t right and your production application will start failing when they are shipped.

To work around this we need to tweak some configuration in Sorbet. In this case I configured the app to raise runtime errors when a signature wasn’t correct but only in test or in staging environment – not in production. To do this you implement a:

call_validation_error_handler

This allows you to control how sorbet reacts to a method receiving or returning a type which doesn’t match the sig annotation.

Here is the initializer I ended up with:

# typed: strict

require "sorbet-runtime"

# Register call_validation_error_handler callback.
# This runs every time a method with a sig fails to type check at runtime.
# See: https://sorbet.org/docs/runtime#on_failure-changing-what-happens-on-runtime-errors

# In all environments report a sig violation to Sentry.
# In any non-production environment raise an error if a sig is violated.
T::Configuration.call_validation_error_handler = lambda do |signature, opts|
failure_message = opts[:pretty_message]
Scrolls.log(at: :sorbet_runtime_sig_checking, msg: failure_message.squish)
error = TypeError.new(failure_message)
track_error_in_sentry!(error)
raise error unless Rails.env.production?
end

Docs: https://sorbet.org/docs/runtime#on_failure-changing-what-happens-on-runtime-errors

There you go, ship the new code with its sigs and keep an eye on Sentry to see if any need tweaking based on real production usage without those causing production errors.

Standard
#terraform, Apps, Azure, Coding, How to

Azure Functions Get Key from Terraform without InternalServerError

So you’re trying to use the Terraform azurerm_function_app_host_keys resource to get the keys from an Azure function after deployment. Sadly, as of 03/2021, this can fail intermittently 😢 (See issue 1 and 2).+

[Edit: Hopefully this issue is resolved by this PR once released so worth reviewing once the change is released]

These errors can look something like these below:

Error making Read request on AzureRM Function App Hostkeys “***”: web.AppsClient#ListHostKeys: Failure responding to request: StatusCode=400 — Original Error: autorest/azure: Service returned an error. Status=400 Code=”BadRequest” Message=”Encountered an error (ServiceUnavailable) from host runtime”

Error: Error making Read request on AzureRM Function App Hostkeys “somefunx”: web.AppsClient#ListHostKeys: Failure responding to request: StatusCode=400

You can work around this by using my previous workaround with ARM templates but it’s a bit clunky so I was looking at another way to do it.

There is an AWESOME project by Scott Winkler called Shell Provider, it lets you write a custom Terraform provider using scripts. You can implement data types and full resources with CRUD support.

Looking into the errors returned by the azurerm_function_app_host_keys resource they’re intermittent and look like they’re related to a timing issue. Did you know the curl command support retrying out of the box?

So using the Shell provider we can create a simple script to make the REST request to the Azure API and use curls inbuilt retry support to have the request retried with an exponential back-off until it succeeds or 5mins is up!

Warning: This script uses –retry-all-errors which is only available in v7.71 and above. The version shipped with the distro your using might not be up-to-date user curl --version to check.

Here is a rough example of what you end up with:

terraform {
required_providers {
shell = {
source = "scottwinkler/shell"
version = "1.7.7"
}
}
}
resource "azurerm_function_app" "functions" {
name = "${var.function_name}${var.random_string}-premium"
location = var.resource_group_location
resource_group_name = var.resource_group_name
app_service_plan_id = var.app_service_plan_id
version = "~3"
storage_account_name = var.storage_account_name
storage_account_access_key = var.storage_account_key
identity {
type = "SystemAssigned"
}
site_config {
# Ensure we use all the mem on the box and not only 3.5GB of it!
use_32_bit_worker_process = false
pre_warmed_instance_count = 1
}
app_settings = merge({
StorageContainerName = var.test_storage_container_name
https_only = true
FUNCTIONS_WORKER_RUNTIME = "dotnet"
HASH = base64encode(filesha256(local.func_zip_path))
WEBSITE_RUN_FROM_PACKAGE = "https://${var.storage_account_name}.blob.core.windows.net/${var.deployment_container_name}/${azurerm_storage_blob.appcode.name}${var.storage_sas}"
# Route outbound requests over VNET see: https://docs.microsoft.com/en-us/azure/azure-functions/functions-networking-options#regional-virtual-network-integration
WEBSITE_DNS_SERVER = "168.63.129.16"
WEBSITE_VNET_ROUTE_ALL = 1
}, var.app_settings)
}
data "azurerm_subscription" "current" {
}
data "shell_script" "functions_key" {
lifecycle_commands {
read = file("${path.module}/readkey.sh")
}
environment = {
FUNC_NAME = azurerm_function_app.functions.name
RG_NAME = var.resource_group_name
SUB_ID = data.azurerm_subscription.current.subscription_id
}
depends_on = [azurerm_function_app.functions]
}
view raw main.tf hosted with ❤ by GitHub
output "function_master_key" {
# Try is used here to ensure destroy works as expected. On destroy the map will be
# empty so try instead returns an empty string
# See: https://www.terraform.io/docs/language/functions/try.html
value = try(data.shell_script.functions_key.output["masterKey"], "")
}
output "function_hostname" {
value = azurerm_function_app.functions.default_hostname
}
output "function_name" {
value = azurerm_function_app.functions.name
}
view raw output.tf hosted with ❤ by GitHub
#!/bin/bash
set -e
# Get a token so we can call the ARM api
TOKEN=$(az account get-access-token -o json | jq -r .accessToken)
# Attempt to list the keys with exponential backoff and do this for 5mins max
# –fail required see https://github.com/curl/curl/issues/6712
curl "https://management.azure.com/subscriptions/$SUB_ID/resourceGroups/$RG_NAME/providers/Microsoft.Web/sites/$FUNC_NAME/host/default/listkeys?api-version=2018-11-01" \
–compressed -H 'Content-Type: application/json;charset=utf-8' \
-H "Authorization: Bearer $TOKEN" -d "{}" \
–retry 8 –retry-max-time 360 –retry-all-errors –fail –silent
view raw readkeys.sh hosted with ❤ by GitHub

Standard