{"id":21915603,"url":"https://github.com/clustercockpit/cc-units","last_synced_at":"2026-02-21T11:31:02.457Z","repository":{"id":117957211,"uuid":"470202281","full_name":"ClusterCockpit/cc-units","owner":"ClusterCockpit","description":"Unit system for the ClusterCockpit monitoring stack","archived":false,"fork":false,"pushed_at":"2025-04-17T23:16:44.000Z","size":38,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-18T11:44:30.132Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ClusterCockpit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-15T14:42:19.000Z","updated_at":"2025-04-17T23:16:47.000Z","dependencies_parsed_at":null,"dependency_job_id":"7d203a15-25ef-4a31-8b75-16035cdd7a7e","html_url":"https://github.com/ClusterCockpit/cc-units","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/ClusterCockpit/cc-units","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClusterCockpit%2Fcc-units","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClusterCockpit%2Fcc-units/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClusterCockpit%2Fcc-units/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClusterCockpit%2Fcc-units/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ClusterCockpit","download_url":"https://codeload.github.com/ClusterCockpit/cc-units/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClusterCockpit%2Fcc-units/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271998881,"owners_count":24856128,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-25T02:00:12.092Z","response_time":1107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-28T19:13:03.670Z","updated_at":"2026-02-21T11:31:02.444Z","avatar_url":"https://github.com/ClusterCockpit.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cc-units - A unit system for ClusterCockpit\n\nWhen working with metrics, the problem comes up that they may use different unit name but have the same unit in fact. There are a lot of real world examples like 'kB' and 'Kbyte'. In [cc-metric-collector](https://github.com/ClusterCockpit/cc-metric-collector), the collectors read data from different sources which may use different units or the programmer specifies a unit for a metric by hand. The cc-units system is not comparable with the SI unit system. If you are looking for a package for the SI units, see [here](https://pkg.go.dev/github.com/gurre/si).\n\nIn order to enable unit comparison and conversion, the ccUnits package provides some helpers:\n```go\nNewUnit(unit string) Unit // create a new unit from some string like 'GHz', 'Mbyte' or 'kevents/s'\nfunc GetUnitUnitFactor(in Unit, out Unit) (func(value float64) float64, error) // Get conversion function between two units\nfunc GetPrefixFactor(in Prefix, out Prefix) func(value float64) float64 // Get conversion function between two prefixes\nfunc GetUnitPrefixFactor(in Unit, out Prefix) (func(value float64) float64, Unit) // Get conversion function for prefix changes and the new unit for further use\n\ntype Unit interface {\n\tValid() bool\n\tString() string\n\tShort() string\n\tAddUnitDenominator(div Measure)\n}\n```\n\nIn order to get the \"normalized\" string unit back or test for validity, you can use:\n```go\nu := NewUnit(\"MB\")\nfmt.Println(u.Valid())                   // true\nfmt.Printf(\"Long string %q\", u.String()) // MegaBytes\nfmt.Printf(\"Short string %q\", u.Short()) // MBytes\nv := NewUnit(\"foo\")\nfmt.Println(v.Valid())                   // false\n```\n\nIf you have two units or other components and need the conversion function:\n```go\n// Get conversion functions for 'kB' to 'MBytes'\nu1 := NewUnit(\"kB\")\nu2 := NewUnit(\"MBytes\")\nconvFunc, err := GetUnitUnitFactor(u1, u2) // Returns an error if the units have different measures\nif err == nil {\n    v2 := convFunc(v1)\n\tfmt.Printf(\"%f %s\\n\", v2, u2.Short())\n}\n// Get conversion function for 'kB' -\u003e 'G' prefix.\n// Returns the function and the new unit 'GBytes'\np1 := NewPrefix(\"G\")\nconvFunc, u_p1 := GetUnitPrefixFactor(u1, p1)\n// or\n// convFunc, u_p1 := GetUnitPrefixStringFactor(u1, \"G\")\nif convFunc != nil {\n\tv2 := convFunc(v1)\n\tfmt.Printf(\"%f %s\\n\", v2, u_p1.Short())\n}\n// Get conversion function for two prefixes: 'G' -\u003e 'T'\np2 := NewPrefix(\"T\")\nconvFunc = GetPrefixPrefixFactor(p1, p2)\nif convFunc != nil {\n\tv2 := convFunc(v1)\n\tfmt.Printf(\"%f %s -\u003e %f %s\\n\", v1, p1.Prefix(), v2, p2.Prefix())\n}\n\n\n```\n\n(In the ClusterCockpit ecosystem the separation between values and units if useful since they are commonly not stored as a single entity but the value is a field in the CCMetric while unit is a tag or a meta information).\n\nIf you have a metric and want the derivation to a bandwidth or events per second, you can use the original unit:\n\n```go\nin_unit, err := metric.GetMeta(\"unit\")\nif err == nil {\n    value, ok := metric.GetField(\"value\")\n    if ok {\n        out_unit = NewUnit(in_unit)\n        out_unit.AddUnitDenominator(\"seconds\")\n\t\tseconds := timeDiff.Seconds()\n        y, err := lp.New(metric.Name()+\"_bw\",\n                         metric.Tags(),\n                         metric.Meta(),\n                         map[string]interface{\"value\": value/seconds},\n                         metric.Time())\n        if err == nil {\n            y.AddMeta(\"unit\", out_unit.Short())\n        }\n    }\n}\n```\n\n## Special unit detection\n\nSome used measures like Bytes and Flops are non-dividable. Consequently there prefixes like Milli, Micro and Nano are not useful. This is quite handy since a unit `mb` for `MBytes` is not uncommon but would by default be parsed as \"MilliBytes\".\n\nSpecial parsing rules for the following measures: iff `prefix==Milli`, use `prefix==Mega`\n  - `Bytes`\n  - `Flops`\n  - `Packets`\n  - `Events`\n  - `Cycles`\n  - `Requests`\n\nThis means the prefixes `Micro` (like `ubytes`) and `Nano` like (`nflops/sec`) are not allowed and return an invalid unit. But you can specify `mflops` and `mb`.\n\nPrefixes for `%` or `percent` are ignored.\n\n## Supported prefixes\n\n```go\nconst (\n\tBase  Prefix = 1\n\tExa          = 1e18\n\tPeta         = 1e15\n\tTera         = 1e12\n\tGiga         = 1e9\n\tMega         = 1e6\n\tKilo         = 1e3\n\tMilli        = 1e-3\n\tMicro        = 1e-6\n\tNano         = 1e-9\n\tKibi         = 1024\n\tMebi         = 1024 * 1024\n\tGibi         = 1024 * 1024 * 1024\n\tTebi         = 1024 * 1024 * 1024 * 1024\n)\n```\n\nThe prefixes are detected using a regular expression `^([kKmMgGtTpP]?[i]?)(.*)` that splits the prefix from the measure. You probably don't need to deal with the prefixes in the code.\n\n## Supported measures\n\n```go\nconst (\n\tNone Measure = iota\n\tBytes\n\tFlops\n\tPercentage\n\tTemperatureC\n\tTemperatureF\n\tRotation\n\tHertz\n\tTime\n\tWatt\n\tJoule\n\tCycles\n\tRequests\n\tPackets\n\tEvents\n)\n```\n\nThere a regular expression for each of the measures like `^([bB][yY]?[tT]?[eE]?[sS]?)` for the `Bytes` measure. \n\n\n## New units\n\nIf the selected units are not suitable for your metric, feel free to send a PR.\n\n### New prefix\n\nFor a new prefix, add it to the big `const` in `ccUnitPrefix.go` and adjust the prefix-unit-splitting regular expression. Afterwards, you have to add cases to the three functions `String()`, `Prefix()` and `NewPrefix()`. `NewPrefix()` contains the parser (`k` or `K` -\u003e `Kilo`). The other one are used for output. `String()` outputs a longer version of the prefix (`Kilo`), while `Prefix()` returns only the short notation (`K`).\n\n### New measure\n\nAdding new prefixes is probably rare but adding a new measure is a more common task. At first, add it to the big `const` in `ccUnitMeasure.go`. Moreover, create a regular expression matching the measure (and pre-compile it like the others). Add the expression matching to `NewMeasure()`. The `String()` and `Short()` functions return descriptive strings for the measure in long form (like `Hertz`) and short form (like `Hz`).\n\nIf there are special conversation rules between measures and you want to convert one measure to another, like temperatures in Celsius to Fahrenheit, a special case in `GetUnitPrefixFactor()` is required.\n\n### Special parsing rules\n\nThe two parsers for prefix and measure are called under the hood by `NewUnit()` and there might some special rules apply. Like in the above section about 'special unit detection', special rules for your new measure might be required. Currently there are two special cases:\n\n- Measures that are non-dividable like Flops, Bytes, Events, ... cannot use `Milli`, `Micro` and `Nano`. The prefix `m` is forced to `M` for these measures\n- If the prefix is `p`/`P` (`Peta`) or `e`/`E` (`Exa`) and the measure is not detectable, it retries detection with the prefix. So first round it tries, for example, prefix `p` and measure `ackets` which fails, so it retries the detection with measure `packets` and `\u003cempty\u003e` prefix (resolves to `Base` prefix).\n\n## Limitations\n\nThe `ccUnits` package is a simple implemtation of a unit system and comes with some limitations:\n\n- The unit denominator (like `s` in `Mbyte/s`) can only have the `Base` prefix, you cannot specify `Byte/ms` for \"Bytes per milli second\".\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclustercockpit%2Fcc-units","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclustercockpit%2Fcc-units","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclustercockpit%2Fcc-units/lists"}