Units named by their UCUM codes (spec/05 §4.1, D-138).
A unit is named by its code in the Unified Code for Units of Measure — UCUM,
https://ucum.org/ucum, version 2.1, Regenstrief Institute — the case-sensitive,
unambiguous codes OpenTelemetry's semantic conventions use: s, ms, By, kBy,
MiBy, %, Cel, kW.h, {request}/s. One code per unit is the point: a source that
says "s" and one that says "sec" must not draw two labels, so a code is checked,
never guessed at.
This module reads the subset of UCUM's grammar (§2.1 of the specification, Syntax
rules) that measurement data uses — products ., quotients / (left-associative,
§2.1.3), signed integer exponents, integer factors and {annotation} blocks (§2.1.5)
— over a fixed table of prefixes (k M G m u n Ki Mi Gi) and atoms (s min h d By bit % Hz Cel [degF] K m g W J Pa). A symbol is matched whole before it is split into a
prefix and an atom (§2.1.4), so min is the minute, and a prefix is refused on an
atom UCUM does not call metric (kmin). Parentheses and UCUM's other atoms are not
read; the table grows by proposal.
A code has a dimension and an exact factor to its base (the second, the byte, the
metre, the gram, the kelvin), so values scale between two codes of one dimension —
1,500 ms is 1.5 s. Cel and [degF] are UCUM's special units, scales with an
offset, and have no factor: conversion between unit systems is out of scope.
Examples
iex> Visualize.Unit.valid?("kW.h")
true
iex> Visualize.Unit.parse("sec")
{:error, {:unknown_atom, "sec"}}
iex> Visualize.Unit.symbol("By/s")
"B/s"
iex> Visualize.Unit.convert(1500, "ms", "s")
1.5
iex> Visualize.Unit.from_telemetry(:millisecond)
{:ok, "ms"}
Summary
Types
A UCUM code, such as ms or {request}/s.
One component of a code: a prefixed atom, an integer factor or an annotation standing alone, with its exponent — negated when the component divides.
A base dimension and its exponent; %{} is dimensionless.
An exact factor, {numerator, denominator}.
Why a string is not a code.
Functions
A value in from written in to: value × factor(from) / factor(to), for two codes
of one dimension that both have a factor. Raises ArgumentError otherwise — between
unit systems (Cel and [degF]) above all, which is out of scope.
The dimension of a code: each base dimension's exponent, %{} when dimensionless.
Raises ArgumentError for a string that is not a code.
The exact factor of a code to its base, {numerator, denominator}, or nil for a code
holding a special unit (Cel, [degF]). Raises ArgumentError for a string that is
not a code.
The code of a Telemetry.Metrics unit — its unit atom, or the to of a {from, to}
conversion — with no dependency on telemetry_metrics (spec/05 §4.1). :native has
no fixed size until a definition converts it, and an atom not in the table is refused
rather than guessed at.
The components of a code, or why it is not one (spec/05 §4.4).
The display symbol of a code (spec/05 §4.1): B for By, µs for us, °C for
Cel, kW·h for kW.h, m² for m2; the factor 1 and annotations are dropped, so
1 and {request} are "".
Whether a string is a code parse/1 reads.
Types
@type code() :: String.t()
A UCUM code, such as ms or {request}/s.
@type component() :: %{ prefix: String.t() | nil, atom: String.t() | nil, factor: pos_integer() | nil, annotation: String.t() | nil, exponent: integer() }
One component of a code: a prefixed atom, an integer factor or an annotation standing alone, with its exponent — negated when the component divides.
@type dimension() :: %{ optional(:time | :information | :length | :mass | :temperature) => integer() }
A base dimension and its exponent; %{} is dimensionless.
@type ratio() :: {pos_integer(), pos_integer()}
An exact factor, {numerator, denominator}.
@type reason() :: :empty | {:syntax, String.t()} | {:unknown_atom, String.t()} | {:not_metric, String.t(), String.t()}
Why a string is not a code.
Functions
A value in from written in to: value × factor(from) / factor(to), for two codes
of one dimension that both have a factor. Raises ArgumentError otherwise — between
unit systems (Cel and [degF]) above all, which is out of scope.
Examples
iex> Visualize.Unit.convert(2_500_000, "By", "MBy")
2.5
The dimension of a code: each base dimension's exponent, %{} when dimensionless.
Raises ArgumentError for a string that is not a code.
The exact factor of a code to its base, {numerator, denominator}, or nil for a code
holding a special unit (Cel, [degF]). Raises ArgumentError for a string that is
not a code.
@spec from_telemetry(atom() | {atom(), atom()}) :: {:ok, code()} | {:error, :native | {:unknown, term()}}
The code of a Telemetry.Metrics unit — its unit atom, or the to of a {from, to}
conversion — with no dependency on telemetry_metrics (spec/05 §4.1). :native has
no fixed size until a definition converts it, and an atom not in the table is refused
rather than guessed at.
Examples
iex> Visualize.Unit.from_telemetry({:native, :microsecond})
{:ok, "us"}
iex> Visualize.Unit.from_telemetry(:native)
{:error, :native}
The components of a code, or why it is not one (spec/05 §4.4).
Examples
iex> Visualize.Unit.parse("km/h")
{:ok,
[
%{prefix: "k", atom: "m", factor: nil, annotation: nil, exponent: 1},
%{prefix: nil, atom: "h", factor: nil, annotation: nil, exponent: -1}
]}
iex> Visualize.Unit.parse("kmin")
{:error, {:not_metric, "k", "min"}}
The display symbol of a code (spec/05 §4.1): B for By, µs for us, °C for
Cel, kW·h for kW.h, m² for m2; the factor 1 and annotations are dropped, so
1 and {request} are "".
Options
:annotations- write an annotation standing alone as its text ({request}/s→request/s), defaultfalse
Examples
iex> Visualize.Unit.symbol("us")
"µs"
iex> Visualize.Unit.symbol("{request}/s", annotations: true)
"request/s"
Whether a string is a code parse/1 reads.