Category: Uncategorised

  • Prometheus – Quick start for a newbie!

    In the world of oberservability, the winner seems, by-a-mile, to be Prometheus.

    The purpose of this series is to explore how Prometheus works, its different type of metrics, etc.

    1) Quickstart 2) Adding basic metrics to our sample Go application 3) Adding custom metrics to our sample Go application

    Quickstart

    On my Ubuntu box I simply followed the quickstart guide

    curl --location -Ok https://github.com/prometheus/prometheus/releases/download/v3.2.0/prometheus-3.2.0.linux-amd64.tar.gz
    ./promtheus

    The quickstart guide says we can see a list Prometheus’s own metrics (Promception!) by visiting the /metrics URL:

    alan@APZ-OMEN:/mnt/c/Users/alan$ curl -s http://172.22.202.216:9090/metrics | more
    # HELP go_gc_cycles_automatic_gc_cycles_total Count of completed GC cycles generated by the Go runtime. Sourced from /gc/cycles/automatic:gc-cycles.
    # TYPE go_gc_cycles_automatic_gc_cycles_total counter
    go_gc_cycles_automatic_gc_cycles_total 10
    # HELP go_gc_cycles_forced_gc_cycles_total Count of completed GC cycles forced by the application. Sourced from /gc/cycles/forced:gc-cycles.
    # TYPE go_gc_cycles_forced_gc_cycles_total counter
    go_gc_cycles_forced_gc_cycles_total 0
    # HELP go_gc_cycles_total_gc_cycles_total Count of all completed GC cycles. Sourced from /gc/cycles/total:gc-cycles.
    # TYPE go_gc_cycles_total_gc_cycles_total counter
    go_gc_cycles_total_gc_cycles_total 10
    # HELP go_gc_duration_seconds A summary of the wall-time pause (stop-the-world) duration in garbage collection cycles.
    # TYPE go_gc_duration_seconds summary
    go_gc_duration_seconds{quantile="0"} 3.3353e-05
    go_gc_duration_seconds{quantile="0.25"} 4.5974e-05
    go_gc_duration_seconds{quantile="0.5"} 0.000163919
    go_gc_duration_seconds{quantile="0.75"} 0.000247898
    go_gc_duration_seconds{quantile="1"} 0.000265798
    go_gc_duration_seconds_sum 0.00141824
    go_gc_duration_seconds_count 10

    The quickstart goes on to say we can query the history of a metric by pasting it into the query box and running execute -> we tried go_gc_cycles_forced_gc_cycles_total and it seems to work !

    One thing we see: the results are annotated with “instance” and “job” labels – this answers our question about how Prometheus handles multiple applications exposing the same metric.

  • Typescript Overloading

    I spent a couple of hours recently trying to solve a problem with overloading in TypeScript only to realise there wasn’t actually a problem at all – I had just completely misunderstood how overloading works !

    Unlike other languages where an overload acts as an “alias” for the implementing method, in TypeScript an overload “replaces” / hides the implementing method – the implementing method becomes inaccessible unless called via an overload signature.

    Example

    I’ll try to show a basic example and then go into more details.

    Imagine, for the sake of this example, an equivalent of “parseInt” (that we could call doParseInt) that takes either a number or a string.

    • If a number is passed we return the number directly.
    • If a string is passed however, a second argument “radix” is required.

    We could code this like:

    // Basic function to test overloading: can be called in two ways
    // value is number (radix not used)
    // value is string (radix required)
    
    function doParseInt(value: (number | string), radix?: number): number {
      if (typeof value === 'number') {
        return value;
      }
      if (typeof value === 'string') {
        return parseInt(value, radix);
      }
      throw new Error(`unexpected value: ${value}`);
    }
    

    We can use it like:

    const val1 = doParseInt(123); // OK
    const val2 = doParseInt('123'); // Error - radix is required if value is string
    const val3 = doParseInt(123, 8); // Error - radix is not used if value is number
    const val4 = doParseInt('123', 10); // OK
    const val5 = doParseInt(123 as (number | string)); // OK
    

    This works but is not great from a type-safety point of view – we have no way of forcing to people to pass a radix if value is string, or likewise from not passing a radix if value is numeric.

    Overloading to the rescue – We add our overload signatures to refine the way we can call the implementing function:

    function doParseInt(value: number): number;
    function doParseInt(value: string, radix: number): number;
    

    Note these are not “functions” – they do not have a body!

    They serve purely as “overload signatures” – method signatures that refine (limit) how we can call the implementing method.

    After adding our signatures, everything seems great except “val5” which no longer compiles – it appears the fact of adding an overload signature has made the original implementing method inaccessible !

    const val1 = doParseInt(123); // OK
    // const val2 = doParseInt('123'); // No longer compiles - great
    // const val3 = doParseInt(123, 8); // Likewise, great
    const val4 = doParseInt('123', 10); // OK
    const val5 = doParseInt(123 as (number | string)); // No longer compiles either .... !
    

    Results in:

    error TS2345: Argument of type 'string | number' is not assignable to parameter of type 'number'.
    Type 'string' is not assignable to type 'number'.
    

    The problem comes from val5 – we want to be able to call directly the non-overloaded implementation method.

    However, this is not possible in TypeScript – adding a single overload makes the non-overloaded implementation inaccessible.

    To resolve this, we need to simply add the original method signature as an overload signature of itself.

    The final code looks like:

    function doParseInt(value: number): number;
    
    function doParseInt(value: string, radix: number): number;
    
    function doParseInt(value: (number | string), radix?: number): number;
    
    // Basic function to test overloading: can be called in two ways
    // value is number (radix not used)
    // value is string (radix required)
    function doParseInt(value: (number | string), radix?: number): number {
      if (typeof value === 'number') {
        return value;
      }
      if (typeof value === 'string') {
        return parseInt(value, radix);
      }
      throw new Error(`unexpected value: ${value}`);
    }
    

    In hindsight it seems clearly logical – we add overload signatures to refine/specialise the call, and so it doesn’t make much sense to leave the original implementing method available.

    What would have been great would have been an error message more explicit: Implementing function not available if overload signature exists” for example

    This example is slightly contrived: The actual problem was with array signatures (RxJS combineLatest for example) but that will be the subject of my next post.

  • Hello world!

    Welcome to WordPress. This is your first post. Edit or delete it, then start writing!