├── doc.go ├── .gitignore ├── codereview.cfg ├── .prettierrc.json ├── godev ├── internal │ ├── content │ │ └── testdata │ │ │ ├── text.out │ │ │ ├── 404.html.out │ │ │ ├── json.out │ │ │ ├── teapot.out │ │ │ ├── error.out │ │ │ ├── redirect.out │ │ │ ├── redirect.html.out │ │ │ ├── markdown.md │ │ │ ├── noindex │ │ │ └── noindex.html.out │ │ │ ├── base.html │ │ │ ├── style.css │ │ │ ├── style.css.out │ │ │ ├── index.html.out │ │ │ ├── data.html.out │ │ │ ├── subdir │ │ │ ├── index.html.out │ │ │ └── index.html │ │ │ ├── data.html │ │ │ ├── index.html │ │ │ ├── markdown.md.out │ │ │ └── base.tmpl │ ├── storage │ │ ├── api.go │ │ └── storage_test.go │ ├── log │ │ └── cloudlog.go │ └── middleware │ │ └── middleware.go ├── cmd │ ├── telemetrygodev │ │ ├── testdata │ │ │ └── config.json │ │ ├── cloudbuild.yaml │ │ ├── Dockerfile │ │ ├── deploy-prod.yaml │ │ └── README.md │ └── worker │ │ ├── Dockerfile │ │ └── cloudbuild.yaml ├── devtools │ ├── localstorage.sh │ └── cmd │ │ ├── esbuild │ │ └── main.go │ │ └── copyuploads │ │ └── copyuploads.go └── go.mod ├── internal ├── unionfs │ ├── testdata │ │ ├── dir2 │ │ │ ├── file2 │ │ │ └── file1 │ │ └── dir1 │ │ │ └── file1 │ ├── unionfs.go │ └── unionfs_test.go ├── browser │ ├── README │ └── browser.go ├── content │ ├── shared │ │ ├── static │ │ │ ├── favicon.ico │ │ │ ├── arrow-forward.svg │ │ │ ├── base.min.js │ │ │ ├── treenav.min.css │ │ │ ├── treenav.min.js │ │ │ ├── treenav.min.css.map │ │ │ ├── base.min.js.map │ │ │ ├── chartbrowser.min.css │ │ │ ├── chartbrowser.min.css.map │ │ │ └── treenav.min.js.map │ │ ├── base.ts │ │ ├── _tooltip.ts │ │ ├── privacy.html │ │ ├── _tooltip.css │ │ ├── base.tmpl │ │ ├── treenav.css │ │ ├── base.css │ │ ├── chartbrowser.css │ │ ├── _typography.css │ │ ├── chartbrowser.tmpl │ │ ├── _color.css │ │ └── treenav.ts │ ├── worker │ │ └── index.md │ ├── telemetrygodev │ │ ├── charts.css │ │ ├── allcharts.html │ │ ├── data.html │ │ ├── charts.html │ │ ├── index.css │ │ ├── config.html │ │ ├── index.html │ │ ├── static │ │ │ ├── charts.min.css │ │ │ ├── index.min.css │ │ │ ├── charts.min.css.map │ │ │ └── index.min.css.map │ │ └── privacy.md │ ├── gotelemetryview │ │ ├── static │ │ │ ├── info_black_24dp.svg │ │ │ ├── storage.min.js │ │ │ └── storage.min.js.map │ │ ├── icon.tmpl │ │ ├── storage.ts │ │ └── index.css │ ├── generate.go │ ├── README.md │ └── content.go ├── telemetry │ ├── dateonly.go │ ├── types.go │ ├── proginfo.go │ ├── proginfo_test.go │ └── dir_test.go ├── crashmonitor │ └── monitor_helper_test.go ├── testenv │ ├── testenv_test.go │ └── testenv.go ├── mmap │ ├── mmap_other.go │ ├── mmap.go │ ├── mmap_unix.go │ └── mmap_windows.go ├── config │ ├── testdata │ │ └── config.json │ ├── config_test.go │ └── config.go ├── configstore │ ├── download_windows.go │ ├── download_test.go │ └── download.go ├── configgen │ ├── syslist.go │ ├── validate.go │ └── validate_test.go ├── upload │ ├── first_test.go │ ├── date.go │ ├── Doc.txt │ ├── findwork.go │ └── upload.go ├── configtest │ └── configtest.go ├── counter │ ├── parse.go │ └── concurrent_test.go └── proxy │ └── proxy_test.go ├── config ├── go.mod └── doc.go ├── go.mod ├── .dockerignore ├── .eslintrc.json ├── crashmonitor └── supported.go ├── .stylelintrc.json ├── dir.go ├── npm ├── npx ├── go.sum ├── start_posix.go ├── .gitattributes ├── cmd └── gotelemetry │ ├── internal │ ├── view │ │ └── README.md │ ├── browser │ │ └── browser.go │ └── csv │ │ └── csv.go │ ├── doc.go │ └── help_test.go ├── types_alias.go ├── package.json ├── tsconfig.json ├── start_windows.go ├── CONTRIBUTING.md ├── counter ├── counter_test.go ├── countertest │ ├── countertest.go │ └── countertest_test.go └── doc.go ├── PATENTS ├── mode.go ├── LICENSE └── README.md /doc.go: -------------------------------------------------------------------------------- 1 | package telemetry 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .localstorage -------------------------------------------------------------------------------- /codereview.cfg: -------------------------------------------------------------------------------- 1 | issuerepo: golang/go 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | {"proseWrap": "always"} 2 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/text.out: -------------------------------------------------------------------------------- 1 | Hello, World! -------------------------------------------------------------------------------- /godev/internal/content/testdata/404.html.out: -------------------------------------------------------------------------------- 1 | Not Found 2 | -------------------------------------------------------------------------------- /internal/unionfs/testdata/dir2/file2: -------------------------------------------------------------------------------- 1 | file 2 content 2 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/json.out: -------------------------------------------------------------------------------- 1 | {"Data":"Data"} 2 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/teapot.out: -------------------------------------------------------------------------------- 1 | I'm a teapot 2 | -------------------------------------------------------------------------------- /internal/unionfs/testdata/dir2/file1: -------------------------------------------------------------------------------- 1 | file 1 content from dir 2 -------------------------------------------------------------------------------- /godev/internal/content/testdata/error.out: -------------------------------------------------------------------------------- 1 | Oh no! Bad Request 2 | -------------------------------------------------------------------------------- /internal/unionfs/testdata/dir1/file1: -------------------------------------------------------------------------------- 1 | file 1 content from dir 1 2 | -------------------------------------------------------------------------------- /config/go.mod: -------------------------------------------------------------------------------- 1 | module golang.org/x/telemetry/config 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/redirect.out: -------------------------------------------------------------------------------- 1 | Moved Permanently. 2 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/redirect.html.out: -------------------------------------------------------------------------------- 1 | Moved Permanently. 2 | -------------------------------------------------------------------------------- /internal/browser/README: -------------------------------------------------------------------------------- 1 | This package is a copy of cmd/internal/browser from the go distribution -------------------------------------------------------------------------------- /internal/content/shared/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang/telemetry/HEAD/internal/content/shared/static/favicon.ico -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module golang.org/x/telemetry 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | golang.org/x/mod v0.31.0 7 | golang.org/x/sync v0.19.0 8 | golang.org/x/sys v0.39.0 9 | ) 10 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/markdown.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Page Title 3 | Layout: base.html 4 | --- 5 | 6 | # This is a heading 7 | 8 | ## This is a subheading 9 | 10 | [link](https://go.dev) 11 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/noindex/noindex.html.out: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | noindex.html.out
5 | 
6 | -------------------------------------------------------------------------------- /internal/content/worker/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Go Telemetry Worker 3 | Layout: base.html 4 | --- 5 | 6 | # Go Telemetry Worker 7 | 8 | ## Overview 9 | 10 | This page will provide information about telemetry the telemetry worker. 11 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/base.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | {{template "base" .}} 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .localstorage 3 | node_modules 4 | devtools 5 | .eslint* 6 | .gitignore 7 | .prettier* 8 | .stylelint* 9 | CONTRIBUTING.md 10 | LICENSE 11 | npm 12 | npx 13 | package-lock.json 14 | package.json 15 | PATENTS 16 | README.md 17 | tsconfig.json -------------------------------------------------------------------------------- /godev/internal/content/testdata/style.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2023 The Go Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | a { 8 | color: transparent; 9 | } 10 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/style.css.out: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2023 The Go Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | a { 8 | color: transparent; 9 | } 10 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended", 5 | "prettier" 6 | ], 7 | "parser": "@typescript-eslint/parser", 8 | "plugins": ["@typescript-eslint"], 9 | "root": true, 10 | "ignorePatterns": ["*.min.js"] 11 | } 12 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/index.html.out: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Page Title 8 | 9 | 10 |
11 | 12 |
13 |

Page Title

14 |
15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/data.html.out: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Page Title 8 | 9 | 10 |
11 | 12 |
13 |

Data from Handler

14 |
15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/subdir/index.html.out: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Page Title 8 | 9 | 10 |
11 | 12 |
13 |

Page in subdirectory

14 |
15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /internal/content/telemetrygodev/charts.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2023 The Go Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | 8 | @import url("../shared/chartbrowser.css"); 9 | 10 | .Charts { 11 | margin-top: 1.5rem; 12 | } 13 | -------------------------------------------------------------------------------- /crashmonitor/supported.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package crashmonitor 6 | 7 | // Deprecated: Supported returns true. 8 | // 9 | //go:fix inline 10 | func Supported() bool { return true } 11 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard"], 3 | "rules": { 4 | "declaration-property-value-allowed-list": { 5 | "/color/": ["/^var\\(--/", "transparent"] 6 | }, 7 | "unit-disallowed-list": ["px"], 8 | "selector-class-pattern": "^[a-zA-Z\\-]+$" 9 | }, 10 | "ignoreFiles": ["**/*.min.css"] 11 | } 12 | -------------------------------------------------------------------------------- /internal/content/gotelemetryview/static/info_black_24dp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/content/generate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build ignore 6 | 7 | package main 8 | 9 | import "golang.org/x/telemetry/internal/content" 10 | 11 | func main() { 12 | content.RunESBuild(false) 13 | } 14 | -------------------------------------------------------------------------------- /dir.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package telemetry 6 | 7 | import "golang.org/x/telemetry/internal/telemetry" 8 | 9 | // Dir returns the telemetry directory. 10 | func Dir() string { 11 | return telemetry.Default.Dir() 12 | } 13 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/data.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | {{template "base" .}} 8 | {{define "title"}}Page Title{{end}} 9 | {{define "content"}} 10 |
11 |

{{.}}

12 |
13 | {{end}} 14 | -------------------------------------------------------------------------------- /internal/content/gotelemetryview/icon.tmpl: -------------------------------------------------------------------------------- 1 | {{define "info-icon"}} 2 |
3 | 4 | 11 | 12 |

13 | {{.}} 14 |

15 |
16 | {{end}} 17 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | {{template "base" .}} 8 | {{define "title"}}Page Title{{end}} 9 | {{define "content"}} 10 |
11 |

Page Title

12 |
13 | {{end}} 14 | -------------------------------------------------------------------------------- /internal/telemetry/dateonly.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package telemetry 6 | 7 | // TODO(rfindley): replace uses of DateOnly with time.DateOnly once we no 8 | // longer support building gopls with go 1.19. 9 | const DateOnly = "2006-01-02" 10 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/markdown.md.out: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Page Title 8 | 9 | 10 |
11 |

This is a heading

12 |

This is a subheading

13 |

link

14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /npm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 The Go Authors. All rights reserved. 3 | # Use of this source code is governed by a BSD-style 4 | # license that can be found in the LICENSE file. 5 | 6 | docker run \ 7 | --rm \ 8 | --volume $(pwd):/workspace \ 9 | --workdir /workspace \ 10 | --env NODE_OPTIONS="--dns-result-order=ipv4first" \ 11 | --entrypoint npm \ 12 | node:18.16.0-slim \ 13 | $@ 14 | -------------------------------------------------------------------------------- /npx: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 The Go Authors. All rights reserved. 3 | # Use of this source code is governed by a BSD-style 4 | # license that can be found in the LICENSE file. 5 | 6 | docker run \ 7 | --rm \ 8 | --volume $(pwd):/workspace \ 9 | --workdir /workspace \ 10 | --env NODE_OPTIONS="--dns-result-order=ipv4first" \ 11 | --entrypoint npx \ 12 | node:18.16.0-slim \ 13 | $@ 14 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/subdir/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | {{template "base" .}} 8 | {{define "title"}}Page Title{{end}} 9 | {{define "content"}} 10 |
11 |

Page in subdirectory

12 |
13 | {{end}} 14 | -------------------------------------------------------------------------------- /internal/content/shared/base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2023 The Go Authors. All rights reserved. 4 | * Use of this source code is governed by a BSD-style 5 | * license that can be found in the LICENSE file. 6 | */ 7 | 8 | import { ToolTipController } from "./_tooltip"; 9 | 10 | for (const el of document.querySelectorAll(".js-tooltip")) { 11 | new ToolTipController(el); 12 | } 13 | -------------------------------------------------------------------------------- /internal/content/shared/static/arrow-forward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /internal/content/gotelemetryview/storage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2023 The Go Authors. All rights reserved. 4 | * Use of this source code is governed by a BSD-style 5 | * license that can be found in the LICENSE file. 6 | */ 7 | 8 | (function () { 9 | const closedSections = localStorage.getItem("closed-sections") ?? ""; 10 | const html = document.querySelector("html"); 11 | html?.setAttribute("data-closed-sections", closedSections); 12 | })(); 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= 2 | golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= 3 | golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= 4 | golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 5 | golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= 6 | golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 7 | -------------------------------------------------------------------------------- /godev/internal/content/testdata/base.tmpl: -------------------------------------------------------------------------------- 1 | 6 | 7 | {{define "base"}} 8 | 9 | 10 | 11 | {{block "title" .}}{{.Title}}{{end}} 12 | 13 | 14 |
15 | {{block "content" .}}{{.Content}}{{end}} 16 |
17 | 18 | 19 | {{end}} 20 | -------------------------------------------------------------------------------- /internal/content/gotelemetryview/static/storage.min.js: -------------------------------------------------------------------------------- 1 | // Code generated by esbuild. DO NOT EDIT. 2 | "use strict";(()=>{(function(){let t=localStorage.getItem("closed-sections")??"";document.querySelector("html")?.setAttribute("data-closed-sections",t)})();})(); 3 | /** 4 | * @license 5 | * Copyright 2023 The Go Authors. All rights reserved. 6 | * Use of this source code is governed by a BSD-style 7 | * license that can be found in the LICENSE file. 8 | */ 9 | //# sourceMappingURL=storage.min.js.map 10 | -------------------------------------------------------------------------------- /config/doc.go: -------------------------------------------------------------------------------- 1 | // The config package holds the config.json file defining the Go telemetry 2 | // upload configuration. 3 | // 4 | // An upload configuration specifies the set of values that are permitted in 5 | // telemetry uploads: GOOS, GOARCH, Go version, and per-program counters. 6 | // 7 | // This package contains no actual Go code, and exists only so the config.json 8 | // file can be served by module proxies. 9 | // 10 | // The config.json is generated by golang.org/x/telemetry/internal/configgen. 11 | package config 12 | -------------------------------------------------------------------------------- /start_posix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris 6 | 7 | package telemetry 8 | 9 | import ( 10 | "os/exec" 11 | "syscall" 12 | ) 13 | 14 | func init() { 15 | daemonize = daemonizePosix 16 | } 17 | 18 | func daemonizePosix(cmd *exec.Cmd) { 19 | cmd.SysProcAttr = &syscall.SysProcAttr{ 20 | Setsid: true, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internal/crashmonitor/monitor_helper_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package crashmonitor 6 | 7 | // This file opens back doors for testing. 8 | 9 | var ( 10 | WriteSentinel = writeSentinel 11 | TelemetryCounterName = telemetryCounterName 12 | ) 13 | 14 | func SetIncrementCounter(f func(name string)) { 15 | incrementCounter = f 16 | } 17 | 18 | func SetChildExitHook(f func()) { 19 | childExitHook = f 20 | } 21 | -------------------------------------------------------------------------------- /internal/testenv/testenv_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package testenv 6 | 7 | import ( 8 | "bytes" 9 | "os/exec" 10 | "testing" 11 | ) 12 | 13 | func TestNeedsGo(t *testing.T) { 14 | NeedsGo(t) 15 | out, err := exec.Command("go", "version").Output() 16 | out = bytes.TrimSpace(out) 17 | if err != nil || !bytes.HasPrefix(out, []byte("go version ")) { 18 | t.Errorf("go version failed - %v: %s", err, out) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Treat all files in the repo as binary, with no git magic updating 2 | # line endings. This produces predictable results in different environments. 3 | # 4 | # Windows users contributing to Go will need to use a modern version 5 | # of git and editors capable of LF line endings. 6 | # 7 | # Windows .bat files are known to have multiple bugs when run with LF 8 | # endings. So if they are checked in with CRLF endings, there should 9 | # be a test like the one in test/winbatch.go in the go repository. 10 | # (See golang.org/issue/37791.) 11 | # 12 | # See golang.org/issue/9281. 13 | 14 | * -text 15 | -------------------------------------------------------------------------------- /internal/mmap/mmap_other.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (js && wasm) || wasip1 || plan9 6 | 7 | package mmap 8 | 9 | import ( 10 | "io" 11 | "os" 12 | ) 13 | 14 | // mmapFile on other systems doesn't mmap the file. It just reads everything. 15 | func mmapFile(f *os.File) (*Data, error) { 16 | b, err := io.ReadAll(f) 17 | if err != nil { 18 | return nil, err 19 | } 20 | return &Data{f, b, nil}, nil 21 | } 22 | 23 | func munmapFile(_ *Data) error { 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /internal/content/shared/_tooltip.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2021 The Go Authors. All rights reserved. 4 | * Use of this source code is governed by a BSD-style 5 | * license that can be found in the LICENSE file. 6 | */ 7 | 8 | /** 9 | * ToolTipController handles closing tooltips on external clicks. 10 | */ 11 | export class ToolTipController { 12 | constructor(private el: HTMLDetailsElement) { 13 | document.addEventListener("click", (e) => { 14 | const insideTooltip = this.el.contains(e.target as Element); 15 | if (!insideTooltip) { 16 | this.el.removeAttribute("open"); 17 | } 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cmd/gotelemetry/internal/view/README.md: -------------------------------------------------------------------------------- 1 | # Go Telemetry View 2 | 3 | Telemetry data it is stored in files on the user machine. Users can run the 4 | command `gotelemetry view` to view the data in a browser. The HTML page served 5 | by the command will generate graphs based on the local copies of report uploads 6 | and active counter files. 7 | 8 | ## Development 9 | 10 | The static files are generated with a generator command. You can edit the source 11 | files and run go generate to rebuild them. 12 | 13 | go generate ./content 14 | 15 | Running the server with the `--dev` flag will watch and rebuild the static files 16 | on save. 17 | 18 | go run ./cmd/gotelemetry view --dev 19 | -------------------------------------------------------------------------------- /internal/content/gotelemetryview/static/storage.min.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../storage.ts"], 4 | "sourcesContent": ["/**\n * @license\n * Copyright 2023 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n(function () {\n const closedSections = localStorage.getItem(\"closed-sections\") ?? \"\";\n const html = document.querySelector(\"html\");\n html?.setAttribute(\"data-closed-sections\", closedSections);\n})();\n"], 5 | "mappings": ";oBAOC,UAAY,CACX,IAAMA,EAAiB,aAAa,QAAQ,iBAAiB,GAAK,GACrD,SAAS,cAAc,MAAM,GACpC,aAAa,uBAAwBA,CAAc,CAC3D,GAAG", 6 | "names": ["closedSections"] 7 | } 8 | -------------------------------------------------------------------------------- /types_alias.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package telemetry 6 | 7 | import "golang.org/x/telemetry/internal/telemetry" 8 | 9 | // Common types and directories used by multiple packages. 10 | 11 | // An UploadConfig controls what data is uploaded. 12 | type UploadConfig = telemetry.UploadConfig 13 | 14 | type ProgramConfig = telemetry.ProgramConfig 15 | 16 | type CounterConfig = telemetry.CounterConfig 17 | 18 | // A Report is what's uploaded (or saved locally) 19 | type Report = telemetry.Report 20 | 21 | type ProgramReport = telemetry.ProgramReport 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "eslint": "eslint . --fix", 4 | "stylelint": "stylelint '**/*.css' --fix", 5 | "prettier": "prettier --write **/*.{css,ts,md,yaml} !**/*.min.css", 6 | "all": "run-s --continue-on-error eslint stylelint prettier" 7 | }, 8 | "devDependencies": { 9 | "@typescript-eslint/eslint-plugin": "5.59.6", 10 | "@typescript-eslint/parser": "5.59.6", 11 | "eslint": "8.40.0", 12 | "eslint-config-prettier": "8.8.0", 13 | "npm-run-all": "4.1.5", 14 | "prettier": "2.8.8", 15 | "stylelint": "15.6.2", 16 | "stylelint-config-standard": "33.0.0", 17 | "typescript": "5.0.4" 18 | }, 19 | "dependencies": { 20 | "@observablehq/plot": "0.6.9", 21 | "d3": "7.8.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /internal/content/shared/static/base.min.js: -------------------------------------------------------------------------------- 1 | // Code generated by esbuild. DO NOT EDIT. 2 | "use strict";(()=>{var e=class{constructor(i){this.el=i;document.addEventListener("click",o=>{this.el.contains(o.target)||this.el.removeAttribute("open")})}};for(let t of document.querySelectorAll(".js-tooltip"))new e(t);})(); 3 | /** 4 | * @license 5 | * Copyright 2021 The Go Authors. All rights reserved. 6 | * Use of this source code is governed by a BSD-style 7 | * license that can be found in the LICENSE file. 8 | */ 9 | /** 10 | * @license 11 | * Copyright 2023 The Go Authors. All rights reserved. 12 | * Use of this source code is governed by a BSD-style 13 | * license that can be found in the LICENSE file. 14 | */ 15 | //# sourceMappingURL=base.min.js.map 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 3 | "compilerOptions": { 4 | "target": "ES2022", 5 | "module": "ES2022", 6 | "moduleResolution": "node", 7 | 8 | "strict": true, 9 | "allowUnusedLabels": false, 10 | "allowUnreachableCode": false, 11 | "exactOptionalPropertyTypes": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "noImplicitOverride": true, 14 | "noImplicitReturns": true, 15 | "noPropertyAccessFromIndexSignature": true, 16 | "noUncheckedIndexedAccess": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | 20 | "checkJs": true, 21 | 22 | "esModuleInterop": true, 23 | "skipLibCheck": true, 24 | "forceConsistentCasingInFileNames": true 25 | }, 26 | } -------------------------------------------------------------------------------- /internal/content/telemetrygodev/allcharts.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | {{template "base" .}} 8 | 9 | {{define "title"}}Go Telemetry / Daily Charts{{end}} 10 | 11 | {{define "content"}} 12 | 13 |
14 |
15 |
16 |
17 |

Daily Charts

18 |

View charts for telemetry data.

19 |
20 |
21 |
22 | 23 |
24 |
25 |
    26 | {{range .}} 27 |
  • {{.}}
  • 28 | {{end}} 29 |
30 |
31 |
32 | 33 |
34 | 35 | {{end}} 36 | -------------------------------------------------------------------------------- /internal/content/shared/privacy.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {{block "title" .}}{{.Title}}{{end}} 15 | 16 | 17 | 18 | 19 |
20 |
21 | {{block "content" .}}{{.Content}}{{end}} 22 |
23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /internal/content/telemetrygodev/data.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | {{template "base" .}} 8 | 9 | {{define "title"}}Go Telemetry / Data{{end}} 10 | 11 | {{define "content"}} 12 | 13 |
14 |
15 |
16 |
17 |

Merged daily reports

18 |

Download raw telemetry data.

19 |
20 |
21 |
22 | 23 |
24 |
25 |
    26 | {{range .Dates}} 27 |
  • {{.}}
  • 28 | {{end}} 29 |
30 |
31 |
32 | 33 |
34 | 35 | {{end}} 36 | -------------------------------------------------------------------------------- /start_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build windows 6 | 7 | package telemetry 8 | 9 | import ( 10 | "os/exec" 11 | "syscall" 12 | 13 | "golang.org/x/sys/windows" 14 | ) 15 | 16 | func init() { 17 | daemonize = daemonizeWindows 18 | } 19 | 20 | func daemonizeWindows(cmd *exec.Cmd) { 21 | // Set DETACHED_PROCESS creation flag so that closing 22 | // the console window the parent process was run in 23 | // does not kill the child. 24 | // See documentation of creation flags in the Microsoft documentation: 25 | // https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags 26 | cmd.SysProcAttr = &syscall.SysProcAttr{ 27 | CreationFlags: windows.DETACHED_PROCESS, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/config/testdata/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "v0.0.1-test", 3 | "GOOS": [ 4 | "linux", 5 | "darwin" 6 | ], 7 | "GOARCH": [ 8 | "amd64", 9 | "arm64" 10 | ], 11 | "GoVersion": [ 12 | "go1.20", 13 | "go1.20.1" 14 | ], 15 | "Programs": [ 16 | { 17 | "Name": "golang.org/x/tools/gopls", 18 | "Versions": [ 19 | "v0.10.1", 20 | "v0.11.0" 21 | ], 22 | "Counters": [ 23 | { 24 | "Name": "editor:{emacs,vim,vscode,other}", 25 | "Rate": 0.01 26 | } 27 | ] 28 | }, 29 | { 30 | "Name": "cmd/go", 31 | "Versions": [ 32 | "v0.10.1", 33 | "v0.11.0" 34 | ], 35 | "Counters": [ 36 | { 37 | "Name": "go/buildcache/miss:{0,0.1,0.2,0.5,1,10,100,2,20,5,50}", 38 | "Rate": 0.01 39 | } 40 | ] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /internal/content/shared/static/treenav.min.css: -------------------------------------------------------------------------------- 1 | /* Code generated by esbuild. DO NOT EDIT. */ 2 | .js-Tree ul{list-style:none;padding-left:0}.js-Tree-item ul{display:none}.js-Tree-item{overflow:hidden;text-overflow:ellipsis;padding:.125rem 0}.js-Tree-item[aria-expanded=true] ul{display:block}.js-Tree-item .js-Tree-item{position:relative;padding-left:1.25rem}.js-Tree-item .js-Tree-item[aria-selected=true]:before{background-color:var(--color-brand-primary);border-radius:50%;content:"";display:block;height:.3125rem;left:.4688rem;position:absolute;top:.75rem;width:.3125rem}.js-Tree-item>a{color:var(--color-text-subtle);font-size:.875rem}.js-Tree-item[aria-selected=true]>a{color:var(--color-text)} 3 | /*! 4 | * Copyright 2024 The Go Authors. All rights reserved. 5 | * Use of this source code is governed by a BSD-style 6 | * license that can be found in the LICENSE file. 7 | */ 8 | /*# sourceMappingURL=treenav.min.css.map */ 9 | -------------------------------------------------------------------------------- /internal/content/telemetrygodev/charts.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | {{template "base" .}} 8 | 9 | {{define "title"}}Go Telemetry / {{index .Charts.DateRange 1}}{{end}} 10 | 11 | {{define "content"}} 12 | 13 | 14 |
15 |
16 |
17 |
18 |

{{.ChartTitle}}

19 |

Generated from {{.Charts.NumReports}} reports.

20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 | {{template "chartbrowser" .}} 28 |
29 |
30 |
31 | 32 | 35 | 36 | 37 | {{end}} 38 | -------------------------------------------------------------------------------- /godev/internal/storage/api.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | 6 | "golang.org/x/telemetry/godev/internal/config" 7 | ) 8 | 9 | type API struct { 10 | Upload BucketHandle 11 | Merge BucketHandle 12 | Chart BucketHandle 13 | } 14 | 15 | func NewAPI(ctx context.Context, cfg *config.Config) (*API, error) { 16 | upload, err := NewBucket(ctx, cfg, cfg.UploadBucket) 17 | if err != nil { 18 | return nil, err 19 | } 20 | merge, err := NewBucket(ctx, cfg, cfg.MergedBucket) 21 | if err != nil { 22 | return nil, err 23 | } 24 | chart, err := NewBucket(ctx, cfg, cfg.ChartDataBucket) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return &API{upload, merge, chart}, nil 29 | } 30 | 31 | func NewBucket(ctx context.Context, cfg *config.Config, name string) (BucketHandle, error) { 32 | if cfg.UseGCS { 33 | return NewGCSBucket(ctx, cfg.ProjectID, name) 34 | } 35 | return NewFSBucket(ctx, cfg.LocalStorage, name) 36 | } 37 | -------------------------------------------------------------------------------- /internal/content/README.md: -------------------------------------------------------------------------------- 1 | # Telemetry Content 2 | 3 | This directory contains the templates, styles, scripts, and images used in the 4 | telemetry services. Scripts and styles are transformed and minified by the 5 | generator in [content.go](./content.go). 6 | 7 | ## Scripts & Styles 8 | 9 | The generator command will look for entrypoint scripts and styles, i.e. files 10 | that are not prefixed with an underscore, and minfiy their contents. TypeScript 11 | files are also transformed into JavaScript. See 12 | [devtools/cmd/esbuild](../devtools/cmd/esbuild/main.go) for more information. 13 | 14 | ## Templates 15 | 16 | Use the .html extension to create a new route, or put an index.html file in a 17 | directory with the desired path. Partial templates with the extension .tmpl in 18 | the same directory as the requested page are included in the html/template 19 | execution step to allow for sharing and composing multiple templates. See 20 | [internal/content](../internal/content/content.go) for more information. 21 | -------------------------------------------------------------------------------- /godev/cmd/telemetrygodev/testdata/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "v0.0.1-test", 3 | "GOOS": [ 4 | "linux", 5 | "darwin" 6 | ], 7 | "GOARCH": [ 8 | "amd64", 9 | "arm64" 10 | ], 11 | "GoVersion": [ 12 | "go1.20", 13 | "go1.20.1" 14 | ], 15 | "Programs": [ 16 | { 17 | "Name": "golang.org/x/tools/gopls", 18 | "Versions": [ 19 | "v0.10.1", 20 | "v0.11.0" 21 | ], 22 | "Counters": [ 23 | { 24 | "Name": "editor:{emacs,vim,vscode,other}", 25 | "Rate": 0.01 26 | } 27 | ], 28 | "Stacks": [ 29 | { 30 | "Name": "gopls/bug", 31 | "Rate": 1, 32 | "Depth": 16 33 | } 34 | ] 35 | }, 36 | { 37 | "Name": "cmd/go", 38 | "Versions": [ 39 | "v0.10.1", 40 | "v0.11.0" 41 | ], 42 | "Counters": [ 43 | { 44 | "Name": "go/buildcache/miss:{0,0.1,0.2,0.5,1,10,100,2,20,5,50}", 45 | "Rate": 0.01 46 | } 47 | ] 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /internal/content/shared/static/treenav.min.js: -------------------------------------------------------------------------------- 1 | // Code generated by esbuild. DO NOT EDIT. 2 | "use strict";(()=>{function h(r){let n=r.querySelectorAll(".js-Tree-heading"),s=()=>{let o=[];for(let e of n){let t=e.getBoundingClientRect();t.height&&t.top<80&&o.unshift(e)}o.length==0&&n[0]instanceof HTMLHeadingElement&&(o=[n[0]]);let l=1/0,a=[];for(let e of o){let t=Number(e.tagName[1]);t{clearTimeout(s),s=setTimeout(()=>r(...i),n)}}})(); 3 | /** 4 | * @license 5 | * Copyright 2024 The Go Authors. All rights reserved. 6 | * Use of this source code is governed by a BSD-style 7 | * license that can be found in the LICENSE file. 8 | */ 9 | //# sourceMappingURL=treenav.min.js.map 10 | -------------------------------------------------------------------------------- /internal/content/shared/_tooltip.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2021 The Go Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | .go-Tooltip { 8 | border-radius: var(--border-radius); 9 | cursor: pointer; 10 | display: inline-block; 11 | position: relative; 12 | } 13 | 14 | .go-Tooltip > summary { 15 | list-style: none; 16 | } 17 | 18 | .go-Tooltip > summary::-webkit-details-marker, 19 | .go-Tooltip > summary::marker { 20 | display: none; 21 | } 22 | 23 | .go-Tooltip > summary > img { 24 | vertical-align: text-bottom; 25 | } 26 | 27 | .go-Tooltip p { 28 | background: var(--color-background) 80%; 29 | border: var(--border); 30 | border-radius: var(--border-radius); 31 | color: var(--color-text); 32 | font-size: 0.75rem; 33 | letter-spacing: 0.0187rem; 34 | line-height: 1rem; 35 | padding: 0.5rem; 36 | position: absolute; 37 | top: 1.5rem; 38 | white-space: normal; 39 | width: 12rem; 40 | z-index: 100; 41 | } 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Go 2 | 3 | Go is an open source project. 4 | 5 | It is the work of hundreds of contributors. We appreciate your help! 6 | 7 | ## Filing issues 8 | 9 | When [filing an issue](https://golang.org/issue/new), make sure to answer these 10 | five questions: 11 | 12 | 1. What version of Go are you using (`go version`)? 13 | 2. What operating system and processor architecture are you using? 14 | 3. What did you do? 15 | 4. What did you expect to see? 16 | 5. What did you see instead? 17 | 18 | General questions should go to the 19 | [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead 20 | of the issue tracker. The gophers there will answer or ask you to file an issue 21 | if you've tripped over a bug. 22 | 23 | ## Contributing code 24 | 25 | Please read the 26 | [Contribution Guidelines](https://golang.org/doc/contribute.html) before sending 27 | patches. 28 | 29 | Unless otherwise noted, the Go source files are distributed under the BSD-style 30 | license found in the LICENSE file. 31 | -------------------------------------------------------------------------------- /godev/internal/log/cloudlog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package log 6 | 7 | import ( 8 | "os" 9 | "time" 10 | 11 | "golang.org/x/exp/slog" 12 | ) 13 | 14 | func NewGCPLogHandler() slog.Handler { 15 | return slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ 16 | ReplaceAttr: gcpReplaceAttr, 17 | Level: slog.LevelDebug, 18 | }) 19 | } 20 | 21 | func gcpReplaceAttr(groups []string, a slog.Attr) slog.Attr { 22 | switch a.Key { 23 | case slog.TimeKey: 24 | if a.Value.Kind() == slog.KindTime { 25 | a.Value = slog.StringValue(a.Value.Time().Format(time.RFC3339)) 26 | } 27 | case slog.MessageKey: 28 | a.Key = "message" 29 | case slog.LevelKey: 30 | a.Key = "severity" 31 | case slog.SourceKey: 32 | a.Key = "logging.googleapis.com/sourceLocation" 33 | case "traceID": 34 | a.Key = "logging.googleapis.com/trace" 35 | case "spanID": 36 | a.Key = "logging.googleapis.com/spanId" 37 | } 38 | return a 39 | } 40 | -------------------------------------------------------------------------------- /internal/content/telemetrygodev/index.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2024 The Go Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | @import url("../shared/chartbrowser.css"); 8 | 9 | p { 10 | /* Reset from _typography.css */ 11 | max-width: none; 12 | } 13 | 14 | .Charts { 15 | margin-top: 1rem; 16 | } 17 | .Centered { 18 | display: flex; 19 | justify-content: center; 20 | } 21 | .Charts-heading { 22 | margin: 2.5rem 0 1.5rem; 23 | } 24 | .Charts-heading h2 { 25 | font-weight: normal; 26 | margin: 0; 27 | } 28 | .Charts-heading a { 29 | border: var(--border); 30 | padding: 0.6rem; 31 | border-radius: 0.5rem; 32 | background-color: var(--color-background-highlighted-link); 33 | } 34 | .Charts-heading ul { 35 | margin: 0.5rem 0 0 0; 36 | list-style: none; 37 | display: inline-flex; 38 | padding: 0; 39 | } 40 | .Charts-heading li { 41 | list-style: none; 42 | display: inline-flex; 43 | } 44 | .Charts-heading li:not(:last-child) { 45 | margin-right: 1rem; 46 | } 47 | -------------------------------------------------------------------------------- /cmd/gotelemetry/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Code generated by TestDocHelp; DO NOT EDIT. 6 | 7 | // Gotelemetry is a tool for managing Go telemetry data and settings. 8 | // 9 | // Usage: 10 | // 11 | // gotelemetry [arguments] 12 | // 13 | // The commands are: 14 | // 15 | // on enable telemetry collection and uploading 16 | // local enable telemetry collection but disable uploading 17 | // off disable telemetry collection and uploading 18 | // view run a web viewer for local telemetry data 19 | // env print the current telemetry environment 20 | // clean remove all local telemetry data 21 | // 22 | // Use "gotelemetry help " for details about any command. 23 | // 24 | // The following additional commands are available for diagnostic 25 | // purposes, and may change or be removed in the future: 26 | // 27 | // csv print all known counters 28 | // dump view counter file data 29 | // upload run upload with logging enabled 30 | package main 31 | -------------------------------------------------------------------------------- /internal/mmap/mmap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This package is a lightly modified version of the mmap code 6 | // in github.com/google/codesearch/index. 7 | 8 | // The mmap package provides an abstraction for memory mapping files 9 | // on different platforms. 10 | package mmap 11 | 12 | import ( 13 | "os" 14 | ) 15 | 16 | // The backing file is never closed, so Data 17 | // remains valid for the lifetime of the process. 18 | type Data struct { 19 | // TODO(pjw): might be better to define versions of Data 20 | // for the 3 specializations 21 | f *os.File 22 | Data []byte 23 | // Some windows magic 24 | Windows interface{} 25 | } 26 | 27 | // Mmap maps the given file into memory. 28 | // When remapping a file, pass the most recently returned Data. 29 | func Mmap(f *os.File) (*Data, error) { 30 | return mmapFile(f) 31 | } 32 | 33 | // Munmap unmaps the given file from memory. 34 | func Munmap(d *Data) error { 35 | return munmapFile(d) 36 | } 37 | -------------------------------------------------------------------------------- /internal/content/shared/base.tmpl: -------------------------------------------------------------------------------- 1 | 6 | 7 | {{define "base"}} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {{block "title" .}}{{.Title}}{{end}} 16 | 17 | 18 | 19 | 20 | {{with .Breadcrumbs}} 21 | 30 | {{end}} 31 |
32 | {{block "content" .}}{{.Content}}{{end}} 33 |
34 | 35 | 36 | {{end}} 37 | -------------------------------------------------------------------------------- /internal/content/shared/treenav.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2024 The Go Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | .js-Tree ul { 8 | list-style: none; 9 | padding-left: 0; 10 | } 11 | 12 | .js-Tree-item ul { 13 | display: none; 14 | } 15 | 16 | .js-Tree-item { 17 | overflow: hidden; 18 | text-overflow: ellipsis; 19 | padding: 0.125rem 0 0.125rem 0; 20 | } 21 | 22 | .js-Tree-item[aria-expanded='true'] ul { 23 | display: block; 24 | } 25 | 26 | .js-Tree-item .js-Tree-item { 27 | position: relative; 28 | padding-left: 1.25rem; 29 | } 30 | 31 | .js-Tree-item .js-Tree-item[aria-selected='true']:before { 32 | background-color: var(--color-brand-primary); 33 | border-radius: 50%; 34 | content: ""; 35 | display: block; 36 | height: .3125rem; 37 | left: .4688rem; 38 | position: absolute; 39 | top: .75rem; 40 | width: .3125rem; 41 | } 42 | 43 | .js-Tree-item>a { 44 | color: var(--color-text-subtle); 45 | font-size: .875rem; 46 | } 47 | 48 | .js-Tree-item[aria-selected='true']>a { 49 | color: var(--color-text); 50 | } 51 | 52 | -------------------------------------------------------------------------------- /internal/configstore/download_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build windows 6 | 7 | package configstore 8 | 9 | import ( 10 | "os/exec" 11 | "syscall" 12 | 13 | "golang.org/x/sys/windows" 14 | ) 15 | 16 | func init() { 17 | needNoConsole = needNoConsoleWindows 18 | } 19 | 20 | func needNoConsoleWindows(cmd *exec.Cmd) { 21 | // The uploader main process is likely a daemonized process with no console. 22 | // (see x/telemetry/start_windows.go) The console creation behavior when 23 | // a parent is a console process without console is not clearly documented 24 | // but empirically we observed the new console is created and attached to the 25 | // subprocess in the default setup. 26 | // 27 | // Ensure no new console is attached to the subprocess by setting CREATE_NO_WINDOW. 28 | // https://learn.microsoft.com/en-us/windows/console/creation-of-a-console 29 | // https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags 30 | cmd.SysProcAttr = &syscall.SysProcAttr{ 31 | CreationFlags: windows.CREATE_NO_WINDOW, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/content/shared/static/treenav.min.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../treenav.css"], 4 | "sourcesContent": ["/*!\n * Copyright 2024 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n.js-Tree ul {\n list-style: none;\n padding-left: 0;\n}\n\n.js-Tree-item ul {\n display: none;\n}\n\n.js-Tree-item {\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 0.125rem 0 0.125rem 0;\n}\n\n.js-Tree-item[aria-expanded='true'] ul {\n display: block;\n}\n\n.js-Tree-item .js-Tree-item {\n position: relative;\n padding-left: 1.25rem;\n}\n\n.js-Tree-item .js-Tree-item[aria-selected='true']:before {\n background-color: var(--color-brand-primary);\n border-radius: 50%;\n content: \"\";\n display: block;\n height: .3125rem;\n left: .4688rem;\n position: absolute;\n top: .75rem;\n width: .3125rem;\n}\n\n.js-Tree-item>a {\n color: var(--color-text-subtle);\n font-size: .875rem;\n}\n\n.js-Tree-item[aria-selected='true']>a {\n color: var(--color-text);\n}\n\n"], 5 | "mappings": ";AAMA,YACE,gBACA,eAGF,iBACE,aAGF,cACE,gBACA,uBAjBF,kBAqBA,qCACE,cAGF,4BACE,kBACA,qBAGF,uDACE,4CA/BF,kBAiCE,WACA,cACA,gBACA,cACA,kBACA,WACA,eAGF,gBACE,+BACA,kBAGF,oCACE", 6 | "names": [] 7 | } 8 | -------------------------------------------------------------------------------- /counter/counter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package counter_test 6 | 7 | import ( 8 | "os" 9 | "os/exec" 10 | "testing" 11 | 12 | "golang.org/x/telemetry/counter" 13 | "golang.org/x/telemetry/internal/telemetry" 14 | "golang.org/x/telemetry/internal/testenv" 15 | ) 16 | 17 | const telemetryDirEnvVar = "_COUNTER_TEST_TELEMETRY_DIR" 18 | 19 | func TestMain(m *testing.M) { 20 | if dir := os.Getenv(telemetryDirEnvVar); dir != "" { 21 | // Run for TestOpenAPIMisuse. 22 | telemetry.Default = telemetry.NewDir(dir) 23 | counter.Open() 24 | counter.OpenAndRotate() // should panic 25 | os.Exit(0) 26 | } 27 | os.Exit(m.Run()) 28 | } 29 | 30 | func TestOpenAPIMisuse(t *testing.T) { 31 | testenv.SkipIfUnsupportedPlatform(t) 32 | 33 | // Test that Open and OpenAndRotate cannot be used simultaneously. 34 | exe, err := os.Executable() 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | cmd := exec.Command(exe) 39 | cmd.Env = append(os.Environ(), telemetryDirEnvVar+"="+t.TempDir()) 40 | 41 | if err := cmd.Run(); err == nil { 42 | t.Error("Failed to detect API misuse: no error from calling both Open and OpenAndRotate") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /internal/mmap/mmap_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build unix 6 | 7 | package mmap 8 | 9 | import ( 10 | "fmt" 11 | "io/fs" 12 | "os" 13 | "syscall" 14 | ) 15 | 16 | func mmapFile(f *os.File) (*Data, error) { 17 | st, err := f.Stat() 18 | if err != nil { 19 | return nil, err 20 | } 21 | size := st.Size() 22 | pagesize := int64(os.Getpagesize()) 23 | if int64(int(size+(pagesize-1))) != size+(pagesize-1) { 24 | return nil, fmt.Errorf("%s: too large for mmap", f.Name()) 25 | } 26 | n := int(size) 27 | if n == 0 { 28 | return &Data{f, nil, nil}, nil 29 | } 30 | mmapLength := int(((size + pagesize - 1) / pagesize) * pagesize) // round up to page size 31 | data, err := syscall.Mmap(int(f.Fd()), 0, mmapLength, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) 32 | if err != nil { 33 | return nil, &fs.PathError{Op: "mmap", Path: f.Name(), Err: err} 34 | } 35 | return &Data{f, data[:n], nil}, nil 36 | } 37 | 38 | func munmapFile(d *Data) error { 39 | if len(d.Data) == 0 { 40 | return nil 41 | } 42 | err := syscall.Munmap(d.Data) 43 | if err != nil { 44 | return &fs.PathError{Op: "munmap", Path: d.f.Name(), Err: err} 45 | } 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /internal/content/telemetrygodev/config.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | {{template "base" .}} 8 | 9 | {{define "title"}}Go Telemetry Config{{end}} 10 | 11 | {{define "content"}} 12 |
13 |
14 |
15 |

Chart Config

16 |

17 | The chart config contains the list of approved charts to display on 18 | telemetry.go.dev. The chart config format is documented by the 19 | 20 | chartconfig 21 | package documentation. 22 |

23 |
{{.ChartConfig}}
24 |
25 | 26 |
27 |

Upload Config

28 |

29 | The upload config contains the list of active counters for each program 30 | and allowed report metadata. This is generated from the chart config 31 | above. 32 |

33 | 36 |
{{.UploadConfig}}
37 |
38 |
39 |
40 | {{end}} 41 | -------------------------------------------------------------------------------- /internal/content/shared/static/base.min.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../_tooltip.ts", "../base.ts"], 4 | "sourcesContent": ["/**\n * @license\n * Copyright 2021 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n/**\n * ToolTipController handles closing tooltips on external clicks.\n */\nexport class ToolTipController {\n constructor(private el: HTMLDetailsElement) {\n document.addEventListener(\"click\", (e) => {\n const insideTooltip = this.el.contains(e.target as Element);\n if (!insideTooltip) {\n this.el.removeAttribute(\"open\");\n }\n });\n }\n}\n", "/**\n * @license\n * Copyright 2023 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\nimport { ToolTipController } from \"./_tooltip\";\n\nfor (const el of document.querySelectorAll(\".js-tooltip\")) {\n new ToolTipController(el);\n}\n"], 5 | "mappings": ";mBAUO,IAAMA,EAAN,KAAwB,CAC7B,YAAoBC,EAAwB,CAAxB,QAAAA,EAClB,SAAS,iBAAiB,QAAUC,GAAM,CAClB,KAAK,GAAG,SAASA,EAAE,MAAiB,GAExD,KAAK,GAAG,gBAAgB,MAAM,CAElC,CAAC,CACH,CACF,ECVA,QAAWC,KAAM,SAAS,iBAAqC,aAAa,EAC1E,IAAIC,EAAkBD,CAAE", 6 | "names": ["ToolTipController", "el", "e", "el", "ToolTipController"] 7 | } 8 | -------------------------------------------------------------------------------- /PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the Go project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of Go, where such license applies only to those patent 11 | claims, both currently owned or controlled by Google and acquired in 12 | the future, licensable by Google that are necessarily infringed by this 13 | implementation of Go. This grant does not include claims that would be 14 | infringed only as a consequence of further modification of this 15 | implementation. If you or your agent or exclusive licensee institute or 16 | order or agree to the institution of patent litigation against any 17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging 18 | that this implementation of Go or any code incorporated within this 19 | implementation of Go constitutes direct or contributory patent 20 | infringement, or inducement of patent infringement, then any patent 21 | rights granted to you under this License for this implementation of Go 22 | shall terminate as of the date such litigation is filed. 23 | -------------------------------------------------------------------------------- /internal/content/shared/base.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2023 The Go Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | @import url("./_normalize.css"); 8 | @import url("./_color.css"); 9 | @import url("./_typography.css"); 10 | @import url("./_tooltip.css"); 11 | 12 | :root { 13 | --border: 0.0625rem solid var(--color-border); 14 | --border-radius: 0.25rem; 15 | } 16 | 17 | .Breadcrumb { 18 | background-color: var(--color-background-accented); 19 | } 20 | .Breadcrumb ol { 21 | list-style: none; 22 | align-items: center; 23 | padding: 0; 24 | margin: 1.5rem 0; 25 | display: inline-flex; 26 | } 27 | .Breadcrumb li { 28 | display: flex; 29 | font-size: 0.875rem; 30 | } 31 | .Breadcrumb li:not(:last-child):after { 32 | background: url("./arrow-forward.svg") no-repeat; 33 | content: ""; 34 | display: block; 35 | height: 1rem; 36 | margin: 0 0.8125rem; 37 | width: 1rem; 38 | text-align: center; 39 | } 40 | 41 | .Hero { 42 | background-color: var(--color-background-accented); 43 | padding: 1rem 0; 44 | } 45 | .Hero h1 { 46 | font-size: 2.25rem; 47 | font-weight: normal; 48 | margin: 0; 49 | } 50 | 51 | .Container { 52 | margin: 0 0 5rem; 53 | } 54 | 55 | .Content { 56 | margin: 0 auto; 57 | max-width: 64rem; 58 | padding: 0 1rem; 59 | } 60 | -------------------------------------------------------------------------------- /godev/devtools/localstorage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2023 The Go Authors. All rights reserved. 4 | # Use of this source code is governed by a BSD-style 5 | # license that can be found in the LICENSE file. 6 | set -e 7 | 8 | # Script for running a Google Cloud Storage API emulator. 9 | # 10 | # Connect to the emulator by adding an endpoint option when creating a storage 11 | # client. 12 | # 13 | # client, err := storage.NewClient( 14 | # context.Background(), 15 | # option.WithEndpoint("http://localhost:8081/storage/v1/"), 16 | # ) 17 | # 18 | # Or by setting STORAGE_EMULATOR_HOST=localhost:8081 when running your program. 19 | # 20 | # STORAGE_EMULATOR_HOST=localhost:8081 go run ./cmd/my-command 21 | # 22 | # OR 23 | # 24 | # if err := os.Setenv("STORAGE_EMULATOR_HOST", "localhost:8081"); err != nil { 25 | # log.Fatal(err) 26 | # } 27 | # 28 | # By default, the emulator will read and write from godev/.localstorage. Pass a 29 | # directory to override that location. 30 | # 31 | # ./devtools/localstorage.sh ~/storage 32 | 33 | version=v0.0.0-20230523204811-eccb7d2267b0 34 | port=8081 35 | dir="${GO_TELEMETRY_LOCAL_STORAGE:-.localstorage}" 36 | if [ ! -z "$1" ]; then 37 | dir="$1" 38 | fi 39 | 40 | if ! command -v gcsemulator &> /dev/null 41 | then 42 | echo "Command gcsemulator could not be found. Installing..." 43 | go install github.com/fullstorydev/emulators/storage/cmd/gcsemulator@$version 44 | fi 45 | 46 | gcsemulator -port $port -dir $dir 47 | -------------------------------------------------------------------------------- /godev/cmd/telemetrygodev/cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Go Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | steps: 6 | # Build the container image 7 | - name: "gcr.io/cloud-builders/docker" 8 | args: 9 | - "build" 10 | - "-t" 11 | - "gcr.io/$PROJECT_ID/telemetrygodev:$COMMIT_SHA" 12 | - "-f" 13 | - "godev/cmd/telemetrygodev/Dockerfile" 14 | - "." 15 | # Push the container image to Container Registry 16 | - name: "gcr.io/cloud-builders/docker" 17 | args: 18 | - "push" 19 | - "gcr.io/$PROJECT_ID/telemetrygodev:$COMMIT_SHA" 20 | - name: golang 21 | args: 22 | - "go" 23 | - "run" 24 | - "golang.org/x/website/cmd/locktrigger@latest" 25 | - "--project=$PROJECT_ID" 26 | - "--build=$BUILD_ID" 27 | - "--repo=https://go.googlesource.com/telemetry" 28 | # Deploy container image to Cloud Run 29 | - name: "gcr.io/google.com/cloudsdktool/cloud-sdk" 30 | entrypoint: gcloud 31 | args: 32 | - "run" 33 | - "deploy" 34 | - "$_ENV-telemetry" 35 | - "--image" 36 | - "gcr.io/$PROJECT_ID/telemetrygodev:$COMMIT_SHA" 37 | - "--region" 38 | - "us-central1" 39 | - "--service-account" 40 | - "$_RUN_SERVICE_ACCOUNT" 41 | - "--set-env-vars" 42 | - "GO_TELEMETRY_PROJECT_ID=$PROJECT_ID,GO_TELEMETRY_ENV=$_ENV" 43 | images: 44 | - "gcr.io/$PROJECT_ID/telemetrygodev:$COMMIT_SHA" 45 | -------------------------------------------------------------------------------- /internal/content/shared/chartbrowser.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2024 The Go Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | @import url("../shared/treenav.css"); 8 | 9 | /* Fix tooltip background for dark theme */ 10 | svg g[aria-label="tip"] g { 11 | fill: var(--color-background); 12 | } 13 | 14 | .Chartbrowser-view { 15 | display: flex; 16 | flex-direction: row; 17 | } 18 | .Chartbrowser-index { 19 | flex: 1 1; 20 | padding: 0 1.5rem 0 0; 21 | } 22 | .Chartbrowser-heading { 23 | font-weight: bold; 24 | font-size: 1.25rem; 25 | margin: 0 0 0.5rem 0; 26 | } 27 | .Chartbrowser-index-sticky { 28 | position: sticky; 29 | top: 1rem; 30 | width: 10rem; 31 | } 32 | .Chartbrowser-index-sticky > ul { 33 | position: sticky; 34 | top: 1rem; 35 | margin-top: 0; 36 | } 37 | .Chartbrowser-link { 38 | color: var(--color-text-subtle); 39 | font-size: .875rem; 40 | line-height: 1.5rem; 41 | } 42 | .Chartbrowser-program { 43 | font-weight: normal; 44 | margin: 0 0 1rem 0; 45 | } 46 | .Chartbrowser-program:not(:first-of-type) { 47 | margin-top: 2rem; 48 | } 49 | .Chartbrowser-chart { 50 | background-color: var(--color-background); 51 | border: 1px solid transparent; 52 | margin-bottom: 1rem; 53 | padding: 0.875rem; 54 | box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .3), 0 1px 3px 1px rgba(60, 64, 67, .15); 55 | } 56 | .Chartbrowser-chart-name { 57 | text-align: center; 58 | margin: 0; 59 | } 60 | -------------------------------------------------------------------------------- /internal/content/content.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package content 6 | 7 | import ( 8 | "embed" 9 | "log" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | "runtime" 14 | ) 15 | 16 | //go:embed * 17 | var FS embed.FS 18 | 19 | //go:generate go run generate.go 20 | 21 | // RunESBuild runs esbuild for all content directories. 22 | // If watch is set, RunESBuild instructs esbuild to watch the content 23 | // directories, and runs esbuild in a separate goroutine. 24 | func RunESBuild(watch bool) { 25 | _, file, _, _ := runtime.Caller(0) 26 | curDir := filepath.Dir(file) 27 | cmdDir := filepath.Join(curDir, "..", "..", "godev", "devtools", "cmd", "esbuild") 28 | for _, dir := range []string{"gotelemetryview", "shared", "telemetrygodev"} { 29 | d := filepath.Join(curDir, dir) 30 | args := []string{"run", ".", "--outdir", filepath.Join(d, "static")} 31 | if watch { 32 | args = append(args, "--watch", d) 33 | } 34 | args = append(args, d) 35 | cmd := exec.Command("go", args...) 36 | cmd.Dir = cmdDir 37 | cmd.Stderr = os.Stderr 38 | cmd.Stdout = os.Stdout 39 | if err := cmd.Start(); err != nil { 40 | log.Fatal(err) 41 | } 42 | if watch { 43 | go func() { 44 | if err := cmd.Wait(); err != nil { 45 | log.Fatal(err) 46 | } 47 | }() 48 | } else { 49 | if err := cmd.Wait(); err != nil { 50 | log.Fatal(err) 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mode.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package telemetry 6 | 7 | import "golang.org/x/telemetry/internal/telemetry" 8 | 9 | // Mode returns the current telemetry mode. 10 | // 11 | // The telemetry mode is a global value that controls both the local collection 12 | // and uploading of telemetry data. Possible mode values are: 13 | // - "on": both collection and uploading is enabled 14 | // - "local": collection is enabled, but uploading is disabled 15 | // - "off": both collection and uploading are disabled 16 | // 17 | // When mode is "on", or "local", telemetry data is written to the local file 18 | // system and may be inspected with the [gotelemetry] command. 19 | // 20 | // If an error occurs while reading the telemetry mode from the file system, 21 | // Mode returns the default value "local". 22 | // 23 | // [gotelemetry]: https://pkg.go.dev/golang.org/x/telemetry/cmd/gotelemetry 24 | func Mode() string { 25 | mode, _ := telemetry.Default.Mode() 26 | return mode 27 | } 28 | 29 | // SetMode sets the global telemetry mode to the given value. 30 | // 31 | // See the documentation of [Mode] for a description of the supported mode 32 | // values. 33 | // 34 | // An error is returned if the provided mode value is invalid, or if an error 35 | // occurs while persisting the mode value to the file system. 36 | func SetMode(mode string) error { 37 | return telemetry.Default.SetMode(mode) 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /internal/content/shared/_typography.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2021 The Go Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | body { 8 | background-color: var(--color-background); 9 | color: var(--color-text); 10 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, 11 | sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; 12 | font-size: 1rem; 13 | line-height: normal; 14 | } 15 | 16 | p { 17 | line-height: 1.4375; 18 | max-width: 75ch; 19 | } 20 | 21 | hr { 22 | border: none; 23 | border-bottom: var(--border); 24 | margin: 0; 25 | width: 100%; 26 | } 27 | 28 | code, 29 | pre, 30 | textarea.code { 31 | font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; 32 | font-size: 0.875rem; 33 | line-height: 1.5em; 34 | } 35 | 36 | pre, 37 | textarea.code { 38 | background-color: var(--color-background-accented); 39 | border: var(--border); 40 | border-radius: var(--border-radius); 41 | color: var(--color-text); 42 | overflow-x: auto; 43 | padding: 0.625rem; 44 | tab-size: 4; 45 | white-space: pre; 46 | } 47 | 48 | button, 49 | input, 50 | select, 51 | textarea { 52 | font: inherit; 53 | } 54 | 55 | a, 56 | a:link, 57 | a:visited { 58 | color: var(--color-brand-primary); 59 | text-decoration: none; 60 | } 61 | 62 | a:hover { 63 | color: var(--color-brand-primary); 64 | text-decoration: underline; 65 | } 66 | 67 | a:hover > * { 68 | text-decoration: underline; 69 | } 70 | -------------------------------------------------------------------------------- /internal/content/shared/static/chartbrowser.min.css: -------------------------------------------------------------------------------- 1 | /* Code generated by esbuild. DO NOT EDIT. */ 2 | .js-Tree ul{list-style:none;padding-left:0}.js-Tree-item ul{display:none}.js-Tree-item{overflow:hidden;text-overflow:ellipsis;padding:.125rem 0}.js-Tree-item[aria-expanded=true] ul{display:block}.js-Tree-item .js-Tree-item{position:relative;padding-left:1.25rem}.js-Tree-item .js-Tree-item[aria-selected=true]:before{background-color:var(--color-brand-primary);border-radius:50%;content:"";display:block;height:.3125rem;left:.4688rem;position:absolute;top:.75rem;width:.3125rem}.js-Tree-item>a{color:var(--color-text-subtle);font-size:.875rem}.js-Tree-item[aria-selected=true]>a{color:var(--color-text)}svg g[aria-label=tip] g{fill:var(--color-background)}.Chartbrowser-view{display:flex;flex-direction:row}.Chartbrowser-index{flex:1 1;padding:0 1.5rem 0 0}.Chartbrowser-heading{font-weight:700;font-size:1.25rem;margin:0 0 .5rem}.Chartbrowser-index-sticky{position:sticky;top:1rem;width:10rem}.Chartbrowser-index-sticky>ul{position:sticky;top:1rem;margin-top:0}.Chartbrowser-link{color:var(--color-text-subtle);font-size:.875rem;line-height:1.5rem}.Chartbrowser-program{font-weight:400;margin:0 0 1rem}.Chartbrowser-program:not(:first-of-type){margin-top:2rem}.Chartbrowser-chart{background-color:var(--color-background);border:1px solid transparent;margin-bottom:1rem;padding:.875rem;box-shadow:0 1px 2px #3c40434d,0 1px 3px 1px #3c404326}.Chartbrowser-chart-name{text-align:center;margin:0} 3 | /*! 4 | * Copyright 2024 The Go Authors. All rights reserved. 5 | * Use of this source code is governed by a BSD-style 6 | * license that can be found in the LICENSE file. 7 | */ 8 | /*# sourceMappingURL=chartbrowser.min.css.map */ 9 | -------------------------------------------------------------------------------- /internal/configgen/syslist.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | // knownOS is the list of past, present, and future known GOOS values. 8 | // Do not remove from this list, as it is used for filename matching. 9 | // If you add an entry to this list, look at unixOS, below. 10 | var knownOS = map[string]bool{ 11 | "aix": true, 12 | "android": true, 13 | "darwin": true, 14 | "dragonfly": true, 15 | "freebsd": true, 16 | "hurd": true, 17 | "illumos": true, 18 | "ios": true, 19 | "js": true, 20 | "linux": true, 21 | "nacl": true, 22 | "netbsd": true, 23 | "openbsd": true, 24 | "plan9": true, 25 | "solaris": true, 26 | "wasip1": true, 27 | "windows": true, 28 | "zos": true, 29 | } 30 | 31 | // knownArch is the list of past, present, and future known GOARCH values. 32 | // Do not remove from this list, as it is used for filename matching. 33 | var knownArch = map[string]bool{ 34 | "386": true, 35 | "amd64": true, 36 | "amd64p32": true, 37 | "arm": true, 38 | "armbe": true, 39 | "arm64": true, 40 | "arm64be": true, 41 | "loong64": true, 42 | "mips": true, 43 | "mipsle": true, 44 | "mips64": true, 45 | "mips64le": true, 46 | "mips64p32": true, 47 | "mips64p32le": true, 48 | "ppc": true, 49 | "ppc64": true, 50 | "ppc64le": true, 51 | "riscv": true, 52 | "riscv64": true, 53 | "s390": true, 54 | "s390x": true, 55 | "sparc": true, 56 | "sparc64": true, 57 | "wasm": true, 58 | } 59 | -------------------------------------------------------------------------------- /godev/cmd/worker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Go Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | # This Dockerfile expects the build context to be the repo root. 6 | 7 | # NOTE: don't put anything in /tmp here. It will work locally, 8 | # but Cloud Run mounts something else to /tmp, so anything 9 | # installed here will be shadowed. 10 | 11 | FROM golang:1.24 12 | 13 | LABEL maintainer="Go Telemetry Team " 14 | 15 | #### Preliminaries 16 | 17 | WORKDIR / 18 | 19 | # Create some directories. 20 | 21 | # The worker binary and related files live here. 22 | RUN mkdir /app 23 | 24 | #### Building binaries 25 | 26 | # Set the working directory outside $GOPATH to ensure module mode is enabled. 27 | WORKDIR /telemetry 28 | 29 | # Copy go.mods and go.sums into the container. 30 | # If they don't change, which is the common case, then docker can 31 | # cache these COPYs and the subsequent RUN. 32 | COPY go.mod go.sum ./ 33 | 34 | WORKDIR /telemetry/godev 35 | 36 | COPY go.mod go.sum ./ 37 | 38 | # Download the dependencies. 39 | RUN go mod download 40 | 41 | WORKDIR /telemetry 42 | 43 | # Copy the repo from local machine into Docker client’s current working 44 | # directory, so that we can use it to build the binary. 45 | # See .dockerignore at the repo root for excluded files. 46 | COPY . ./ 47 | 48 | WORKDIR /telemetry/godev 49 | 50 | # Build the worker binary and put it in /app. 51 | RUN go build -mod=readonly -o /app/worker ./cmd/worker 52 | 53 | WORKDIR /telemetry 54 | 55 | COPY config/config.json /app/config.json 56 | 57 | #### worker init 58 | 59 | WORKDIR /app 60 | 61 | ENV GO_TELEMETRY_UPLOAD_CONFIG=/app/config.json 62 | 63 | CMD ["./worker", "--gcs"] 64 | -------------------------------------------------------------------------------- /godev/cmd/telemetrygodev/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Go Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | # This Dockerfile expects the build context to be the repo root. 6 | 7 | # NOTE: don't put anything in /tmp here. It will work locally, 8 | # but Cloud Run mounts something else to /tmp, so anything 9 | # installed here will be shadowed. 10 | 11 | FROM golang:1.24 12 | 13 | LABEL maintainer="Go Telemetry Team " 14 | 15 | #### Preliminaries 16 | 17 | WORKDIR / 18 | 19 | # Create some directories. 20 | 21 | # The telemetrygodev binary and related files live here. 22 | RUN mkdir /app 23 | 24 | #### Building binaries 25 | 26 | # Set the working directory outside $GOPATH to ensure module mode is enabled. 27 | WORKDIR /telemetry 28 | 29 | # Copy go.mods and go.sums into the container. 30 | # If they don't change, which is the common case, then docker can 31 | # cache these COPYs and the subsequent RUN. 32 | COPY go.mod go.sum ./ 33 | 34 | WORKDIR /telemetry/godev 35 | 36 | COPY go.mod go.sum ./ 37 | 38 | # Download the dependencies. 39 | RUN go mod download 40 | 41 | WORKDIR /telemetry 42 | 43 | # Copy the repo from local machine into Docker client’s current working 44 | # directory, so that we can use it to build the binary. 45 | # See .dockerignore at the repo root for excluded files. 46 | COPY . ./ 47 | 48 | WORKDIR /telemetry/godev 49 | 50 | # Build the telemetrygodev binary and put it in /app. 51 | RUN go build -mod=readonly -o /app/telemetrygodev ./cmd/telemetrygodev 52 | 53 | WORKDIR /telemetry 54 | 55 | COPY config/config.json /app/config.json 56 | 57 | #### telemetrygodev init 58 | 59 | WORKDIR /app 60 | 61 | ENV GO_TELEMETRY_UPLOAD_CONFIG=/app/config.json 62 | 63 | CMD ["./telemetrygodev", "--gcs"] 64 | -------------------------------------------------------------------------------- /godev/cmd/worker/cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Go Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | steps: 6 | # Build the container image 7 | - name: "gcr.io/cloud-builders/docker" 8 | args: 9 | - "build" 10 | - "-t" 11 | - "gcr.io/$PROJECT_ID/worker:$COMMIT_SHA" 12 | - "-f" 13 | - "godev/cmd/worker/Dockerfile" 14 | - "." 15 | # Push the container image to Container Registry 16 | - name: "gcr.io/cloud-builders/docker" 17 | args: 18 | - "push" 19 | - "gcr.io/$PROJECT_ID/worker:$COMMIT_SHA" 20 | - name: golang 21 | args: 22 | - "go" 23 | - "run" 24 | - "golang.org/x/website/cmd/locktrigger@latest" 25 | - "--project=$PROJECT_ID" 26 | - "--build=$BUILD_ID" 27 | - "--repo=https://go.googlesource.com/telemetry" 28 | # Deploy container image to Cloud Run 29 | - name: "gcr.io/google.com/cloudsdktool/cloud-sdk" 30 | entrypoint: gcloud 31 | args: 32 | - "run" 33 | - "deploy" 34 | - "$_ENV-worker" 35 | - "--image" 36 | - "gcr.io/$PROJECT_ID/worker:$COMMIT_SHA" 37 | - "--region" 38 | - "us-central1" 39 | - "--service-account" 40 | - "$_RUN_SERVICE_ACCOUNT" 41 | - "--set-env-vars" 42 | - "GO_TELEMETRY_PROJECT_ID=$PROJECT_ID" 43 | - "--set-env-vars" 44 | - "GO_TELEMETRY_ENV=$_ENV" 45 | - "--set-env-vars" 46 | - "GO_TELEMETRY_IAP_SERVICE_ACCOUNT=$_IAP_SERVICE_ACCOUNT" 47 | - "--set-env-vars" 48 | - "GO_TELEMETRY_CLIENT_ID=$_CLIENT_ID" 49 | - "--set-env-vars" 50 | - "GO_TELEMETRY_LOCATION_ID=$_LOCATION_ID" 51 | - "--set-env-vars" 52 | - "GO_TELEMETRY_WORKER_URL=$_WORKER_URL" 53 | images: 54 | - "gcr.io/$PROJECT_ID/worker:$COMMIT_SHA" 55 | -------------------------------------------------------------------------------- /internal/upload/first_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package upload 6 | 7 | import ( 8 | "net/http" 9 | "reflect" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | // make sure we can talk to the test server 15 | // In practice this test runs last, so is somewhat superfluous, 16 | // but it also checks that uploads and reads from the channel are matched 17 | func TestSimpleServer(t *testing.T) { 18 | srv, uploaded := CreateTestUploadServer(t) 19 | 20 | url := srv.URL 21 | resp, err := http.Post(url, "text/plain", strings.NewReader("hello")) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | if resp.StatusCode != http.StatusOK { 26 | t.Errorf("%#v", resp.StatusCode) 27 | } 28 | got := uploaded() 29 | want := [][]byte{[]byte("hello")} 30 | if !reflect.DeepEqual(got, want) { 31 | t.Errorf("got %s, want %s", got, want) 32 | } 33 | } 34 | 35 | // make sure computeRandom() gets enough small values 36 | // in case SamplingRate is as small as .001 37 | func TestRandom(t *testing.T) { 38 | // This test, being statistical, is intrinsically flaky 39 | // It has failed once in its first 3 months at 4.5, 40 | // so change the criterion to 7 sigma. 41 | const N = 102400 // 35msec on an M1 mac 42 | cnt := 0 43 | for i := 0; i < N; i++ { 44 | if computeRandom() < 1.0/1024 { 45 | cnt++ 46 | } 47 | } 48 | // cnt has a binomial distribution. The normal 49 | // approximation has mu=N*p=100, sigma=sqrt(N*p*(1-p))=10 50 | // We reject if cnt is off by 45, which happens about 1/300,000 51 | // if the computeRandom() is truly uniform. That is, the 52 | // test will be flaky about 3 times in a million. 53 | if cnt < 30 || cnt > 170 { 54 | t.Errorf("cnt %d more than 7 sigma(10) from mean(100)", cnt) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/content/telemetrygodev/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | {{template "base" .}} 8 | 9 | {{define "title"}}Go Telemetry{{end}} 10 | 11 | {{define "content"}} 12 | 13 | 14 |
15 |
16 |
17 |
18 |

Go Telemetry 📊

19 |

20 | Go Telemetry is a way for Go toolchain programs to collect 21 | data about their performance and usage. Uploaded data is used to help 22 | improve the Go toolchain and related tools. Go Telemetry is not built 23 | into users' binaries. Learn more about Go telemetry at 24 | go.dev/doc/telemetry. 25 |

26 | 27 |

28 | Users who have opted in will upload an approved subset of telemetry 29 | data approximately once a week. This subset is determined by the current 30 | upload configuration. 31 |

32 | 33 |

34 | For privacy information about this service, see 35 | telemetry.go.dev/privacy. 36 |

37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 |

{{.ChartTitle}}

45 | 49 |
50 | {{template "chartbrowser" .}} 51 |
52 |
53 | 54 |
55 | 56 | 59 | 60 | 61 | {{end}} 62 | -------------------------------------------------------------------------------- /internal/content/shared/chartbrowser.tmpl: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | {{define "chartbrowser"}} 13 |
14 |
15 | 36 |
37 |
38 | {{range .Charts.Programs}} 39 | {{if .Charts}} 40 | {{$progName := programName .Name}} 41 |

{{$progName}}

42 | {{range .Charts}} 43 | {{with .}} 44 |
45 |

{{$progName}} > {{chartName .Name}}

46 |
47 |
48 | {{end}} 49 | {{end}} 50 | {{end}} 51 | {{end}} 52 |
53 |
54 | {{end}} 55 | -------------------------------------------------------------------------------- /internal/content/telemetrygodev/static/charts.min.css: -------------------------------------------------------------------------------- 1 | /* Code generated by esbuild. DO NOT EDIT. */ 2 | .js-Tree ul{list-style:none;padding-left:0}.js-Tree-item ul{display:none}.js-Tree-item{overflow:hidden;text-overflow:ellipsis;padding:.125rem 0}.js-Tree-item[aria-expanded=true] ul{display:block}.js-Tree-item .js-Tree-item{position:relative;padding-left:1.25rem}.js-Tree-item .js-Tree-item[aria-selected=true]:before{background-color:var(--color-brand-primary);border-radius:50%;content:"";display:block;height:.3125rem;left:.4688rem;position:absolute;top:.75rem;width:.3125rem}.js-Tree-item>a{color:var(--color-text-subtle);font-size:.875rem}.js-Tree-item[aria-selected=true]>a{color:var(--color-text)}svg g[aria-label=tip] g{fill:var(--color-background)}.Chartbrowser-view{display:flex;flex-direction:row}.Chartbrowser-index{flex:1 1;padding:0 1.5rem 0 0}.Chartbrowser-heading{font-weight:700;font-size:1.25rem;margin:0 0 .5rem}.Chartbrowser-index-sticky{position:sticky;top:1rem;width:10rem}.Chartbrowser-index-sticky>ul{position:sticky;top:1rem;margin-top:0}.Chartbrowser-link{color:var(--color-text-subtle);font-size:.875rem;line-height:1.5rem}.Chartbrowser-program{font-weight:400;margin:0 0 1rem}.Chartbrowser-program:not(:first-of-type){margin-top:2rem}.Chartbrowser-chart{background-color:var(--color-background);border:1px solid transparent;margin-bottom:1rem;padding:.875rem;box-shadow:0 1px 2px #3c40434d,0 1px 3px 1px #3c404326}.Chartbrowser-chart-name{text-align:center;margin:0}.Charts{margin-top:1.5rem} 3 | /*! 4 | * Copyright 2024 The Go Authors. All rights reserved. 5 | * Use of this source code is governed by a BSD-style 6 | * license that can be found in the LICENSE file. 7 | */ 8 | /*! 9 | * Copyright 2023 The Go Authors. All rights reserved. 10 | * Use of this source code is governed by a BSD-style 11 | * license that can be found in the LICENSE file. 12 | */ 13 | /*# sourceMappingURL=charts.min.css.map */ 14 | -------------------------------------------------------------------------------- /internal/mmap/mmap_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package mmap 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "syscall" 11 | "unsafe" 12 | 13 | "golang.org/x/sys/windows" 14 | ) 15 | 16 | func mmapFile(f *os.File) (*Data, error) { 17 | st, err := f.Stat() 18 | if err != nil { 19 | return nil, err 20 | } 21 | size := st.Size() 22 | if size == 0 { 23 | return &Data{f, nil, nil}, nil 24 | } 25 | // set the min and max sizes to zero to map the whole file, as described in 26 | // https://learn.microsoft.com/en-us/windows/win32/memory/creating-a-file-mapping-object#file-mapping-size 27 | h, err := windows.CreateFileMapping(windows.Handle(f.Fd()), nil, syscall.PAGE_READWRITE, 0, 0, nil) 28 | if err != nil { 29 | return nil, fmt.Errorf("CreateFileMapping %s: %w", f.Name(), err) 30 | } 31 | // the mapping extends from zero to the end of the file mapping 32 | // https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile 33 | addr, err := windows.MapViewOfFile(h, syscall.FILE_MAP_READ|syscall.FILE_MAP_WRITE, 0, 0, 0) 34 | if err != nil { 35 | return nil, fmt.Errorf("MapViewOfFile %s: %w", f.Name(), err) 36 | } 37 | // Note: previously, we called windows.VirtualQuery here to get the exact 38 | // size of the memory mapped region, but VirtualQuery reported sizes smaller 39 | // than the actual file size (hypothesis: VirtualQuery only reports pages in 40 | // a certain state, and newly written pages may not be counted). 41 | return &Data{f, unsafe.Slice((*byte)(unsafe.Pointer(addr)), size), h}, nil 42 | } 43 | 44 | func munmapFile(d *Data) error { 45 | err := windows.UnmapViewOfFile(uintptr(unsafe.Pointer(&d.Data[0]))) 46 | x, ok := d.Windows.(windows.Handle) 47 | if ok { 48 | windows.CloseHandle(x) 49 | } 50 | d.f.Close() 51 | return err 52 | } 53 | -------------------------------------------------------------------------------- /internal/telemetry/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package telemetry 6 | 7 | // Common types and directories used by multiple packages. 8 | 9 | // An UploadConfig controls what data is uploaded. 10 | type UploadConfig struct { 11 | GOOS []string 12 | GOARCH []string 13 | GoVersion []string 14 | SampleRate float64 15 | Programs []*ProgramConfig 16 | } 17 | 18 | type ProgramConfig struct { 19 | // the counter names may have to be 20 | // repeated for each program. (e.g., if the counters are in a package 21 | // that is used in more than one program.) 22 | Name string 23 | Versions []string // versions present in a counterconfig 24 | Counters []CounterConfig `json:",omitempty"` 25 | Stacks []CounterConfig `json:",omitempty"` 26 | } 27 | 28 | type CounterConfig struct { 29 | Name string // The "collapsed" counter: :{,,...} 30 | Rate float64 // If X <= Rate, report this counter 31 | Depth int `json:",omitempty"` // for stack counters 32 | } 33 | 34 | // A Report is the weekly aggregate of counters. 35 | type Report struct { 36 | Week string // End day this report covers (YYYY-MM-DD) 37 | LastWeek string // Week field from latest previous report uploaded 38 | X float64 // A random probability used to determine which counters are uploaded 39 | Programs []*ProgramReport 40 | Config string // version of UploadConfig used 41 | } 42 | 43 | type ProgramReport struct { 44 | Program string // Package path of the program. 45 | Version string // Program version. Go version if the program is part of the go distribution. Module version, otherwise. 46 | GoVersion string // Go version used to build the program. 47 | GOOS string 48 | GOARCH string 49 | Counters map[string]int64 50 | Stacks map[string]int64 51 | } 52 | -------------------------------------------------------------------------------- /internal/configgen/validate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "go/version" 11 | "strings" 12 | 13 | "golang.org/x/mod/semver" 14 | "golang.org/x/telemetry/internal/chartconfig" 15 | "golang.org/x/telemetry/internal/telemetry" 16 | ) 17 | 18 | // ValidateChartConfig checks that a ChartConfig is complete and coherent, 19 | // returning an error describing all problems encountered, or nil. 20 | func ValidateChartConfig(cfg chartconfig.ChartConfig) error { 21 | var errs []error 22 | reportf := func(format string, args ...any) { 23 | errs = append(errs, fmt.Errorf(format, args...)) 24 | } 25 | if cfg.Title == "" { 26 | reportf("title must be set") 27 | } 28 | if len(cfg.Issue) == 0 { 29 | reportf("at least one issue is required") 30 | } 31 | if cfg.Program == "" { 32 | reportf("program must be set") 33 | } 34 | if !telemetry.IsToolchainProgram(cfg.Program) && cfg.Module == "" { 35 | reportf("module must be set") 36 | } else if !strings.HasPrefix(cfg.Program, cfg.Module) { 37 | reportf("module must be a prefix of program: %q doesn't have prefix %q", cfg.Program, cfg.Module) 38 | } 39 | if cfg.Counter == "" { 40 | reportf("counter must be set") 41 | } 42 | if cfg.Type == "" { 43 | reportf("type must be set") 44 | } 45 | if cfg.Depth < 0 { 46 | reportf("invalid depth %d: must be non-negative", cfg.Depth) 47 | } 48 | if cfg.Depth != 0 && cfg.Type != "stack" { 49 | reportf("depth can only be set for \"stack\" chart types") 50 | } 51 | valid := semver.IsValid 52 | if telemetry.IsToolchainProgram(cfg.Program) { 53 | valid = version.IsValid 54 | } 55 | if cfg.Version != "" && !valid(cfg.Version) { 56 | reportf("%q is not a valid version (must be a go version or semver)", cfg.Version) 57 | } 58 | return errors.Join(errs...) 59 | } 60 | -------------------------------------------------------------------------------- /godev/go.mod: -------------------------------------------------------------------------------- 1 | module golang.org/x/telemetry/godev 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | cloud.google.com/go/cloudtasks v1.12.4 7 | cloud.google.com/go/storage v1.30.1 8 | github.com/evanw/esbuild v0.17.19 9 | github.com/google/go-cmp v0.6.0 10 | github.com/yuin/goldmark v1.5.4 11 | golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 12 | golang.org/x/mod v0.31.0 13 | golang.org/x/sync v0.19.0 14 | golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc 15 | google.golang.org/api v0.149.0 16 | ) 17 | 18 | require ( 19 | cloud.google.com/go v0.110.8 // indirect 20 | cloud.google.com/go/compute/metadata v0.3.0 // indirect 21 | cloud.google.com/go/iam v1.1.3 // indirect 22 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 23 | github.com/golang/protobuf v1.5.3 // indirect 24 | github.com/google/s2a-go v0.1.7 // indirect 25 | github.com/google/uuid v1.4.0 // indirect 26 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 27 | github.com/googleapis/gax-go/v2 v2.12.0 // indirect 28 | github.com/kr/pretty v0.1.0 // indirect 29 | go.opencensus.io v0.24.0 // indirect 30 | golang.org/x/crypto v0.45.0 // indirect 31 | golang.org/x/net v0.47.0 // indirect 32 | golang.org/x/oauth2 v0.34.0 // indirect 33 | golang.org/x/text v0.31.0 // indirect 34 | golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect 35 | google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect 36 | google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect 37 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect 38 | google.golang.org/grpc v1.59.0 // indirect 39 | google.golang.org/protobuf v1.34.1 // indirect 40 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 41 | gopkg.in/yaml.v2 v2.4.0 // indirect 42 | ) 43 | 44 | require ( 45 | github.com/yuin/goldmark-meta v1.1.0 46 | golang.org/x/sys v0.39.0 // indirect 47 | ) 48 | 49 | replace golang.org/x/telemetry => ./.. 50 | -------------------------------------------------------------------------------- /internal/browser/browser.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package browser provides utilities for interacting with users' browsers. 6 | package browser 7 | 8 | import ( 9 | "os" 10 | "os/exec" 11 | "runtime" 12 | "time" 13 | ) 14 | 15 | // Commands returns a list of possible commands to use to open a url. 16 | func Commands() [][]string { 17 | var cmds [][]string 18 | if exe := os.Getenv("BROWSER"); exe != "" { 19 | cmds = append(cmds, []string{exe}) 20 | } 21 | switch runtime.GOOS { 22 | case "darwin": 23 | cmds = append(cmds, []string{"/usr/bin/open"}) 24 | case "windows": 25 | cmds = append(cmds, []string{"cmd", "/c", "start"}) 26 | default: 27 | if os.Getenv("DISPLAY") != "" { 28 | // xdg-open is only for use in a desktop environment. 29 | cmds = append(cmds, []string{"xdg-open"}) 30 | } 31 | } 32 | cmds = append(cmds, 33 | []string{"chrome"}, 34 | []string{"google-chrome"}, 35 | []string{"chromium"}, 36 | []string{"firefox"}, 37 | ) 38 | return cmds 39 | } 40 | 41 | // Open tries to open url in a browser and reports whether it succeeded. 42 | func Open(url string) bool { 43 | for _, args := range Commands() { 44 | cmd := exec.Command(args[0], append(args[1:], url)...) 45 | if cmd.Start() == nil && appearsSuccessful(cmd, 3*time.Second) { 46 | return true 47 | } 48 | } 49 | return false 50 | } 51 | 52 | // appearsSuccessful reports whether the command appears to have run successfully. 53 | // If the command runs longer than the timeout, it's deemed successful. 54 | // If the command runs within the timeout, it's deemed successful if it exited cleanly. 55 | func appearsSuccessful(cmd *exec.Cmd, timeout time.Duration) bool { 56 | errc := make(chan error, 1) 57 | go func() { 58 | errc <- cmd.Wait() 59 | }() 60 | 61 | select { 62 | case <-time.After(timeout): 63 | return true 64 | case err := <-errc: 65 | return err == nil 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /internal/configtest/configtest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package configtest provides a helper for testing using a local proxy 6 | // containing a fake upload config. 7 | package configtest 8 | 9 | import ( 10 | "encoding/json" 11 | "fmt" 12 | "os/exec" 13 | "path/filepath" 14 | "testing" 15 | 16 | "golang.org/x/telemetry/internal/configstore" 17 | "golang.org/x/telemetry/internal/proxy" 18 | "golang.org/x/telemetry/internal/telemetry" 19 | ) 20 | 21 | // LocalProxyEnv writes a proxy directory for the given upload config, and 22 | // returns a go environment to use for fetching that config from a local 23 | // file-based proxy. 24 | // 25 | // This environment should be passed to [configstore.Download]. 26 | func LocalProxyEnv(t *testing.T, cfg *telemetry.UploadConfig, version string) []string { 27 | t.Helper() 28 | 29 | dir := t.TempDir() 30 | 31 | encoded, err := json.Marshal(cfg) 32 | if err != nil { 33 | t.Fatalf("marshaling config failed: %v", err) 34 | } 35 | dirPath := fmt.Sprintf("%v@%v/", configstore.ModulePath, version) 36 | files := map[string][]byte{ 37 | dirPath + "go.mod": []byte("module " + configstore.ModulePath + "\n\ngo 1.20\n"), 38 | dirPath + "config.json": encoded, 39 | } 40 | proxyURI, err := proxy.WriteProxy(filepath.Join(dir, "proxy"), files) 41 | if err != nil { 42 | t.Fatalf("writing proxy failed: %v", err) 43 | } 44 | 45 | env := []string{ 46 | "GOPROXY=" + proxyURI, // Use the fake proxy. 47 | "GONOSUMDB=*", // Skip verifying checksum against sum.golang.org. 48 | "GOMODCACHE=" + filepath.Join(dir, "modcache"), // Don't pollute system module cache. 49 | } 50 | t.Cleanup(func() { 51 | cmd := exec.Command("go", "clean", "-modcache") 52 | cmd.Env = append(cmd.Environ(), env...) 53 | out, err := cmd.CombinedOutput() 54 | if err != nil { 55 | t.Errorf("go clean -modcache failed: %v\n%s", err, out) 56 | } 57 | }) 58 | return env 59 | } 60 | -------------------------------------------------------------------------------- /cmd/gotelemetry/internal/browser/browser.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package browser provides utilities for interacting with users' browsers. 6 | // This is a copy of the go project's src/cmd/internal/browser. 7 | package browser 8 | 9 | import ( 10 | "os" 11 | "os/exec" 12 | "runtime" 13 | "time" 14 | ) 15 | 16 | // Commands returns a list of possible commands to use to open a url. 17 | func Commands() [][]string { 18 | var cmds [][]string 19 | if exe := os.Getenv("BROWSER"); exe != "" { 20 | cmds = append(cmds, []string{exe}) 21 | } 22 | switch runtime.GOOS { 23 | case "darwin": 24 | cmds = append(cmds, []string{"/usr/bin/open"}) 25 | case "windows": 26 | cmds = append(cmds, []string{"cmd", "/c", "start"}) 27 | default: 28 | if os.Getenv("DISPLAY") != "" { 29 | // xdg-open is only for use in a desktop environment. 30 | cmds = append(cmds, []string{"xdg-open"}) 31 | } 32 | } 33 | cmds = append(cmds, 34 | []string{"chrome"}, 35 | []string{"google-chrome"}, 36 | []string{"chromium"}, 37 | []string{"firefox"}, 38 | ) 39 | return cmds 40 | } 41 | 42 | // Open tries to open url in a browser and reports whether it succeeded. 43 | func Open(url string) bool { 44 | for _, args := range Commands() { 45 | cmd := exec.Command(args[0], append(args[1:], url)...) 46 | if cmd.Start() == nil && appearsSuccessful(cmd, 3*time.Second) { 47 | return true 48 | } 49 | } 50 | return false 51 | } 52 | 53 | // appearsSuccessful reports whether the command appears to have run successfully. 54 | // If the command runs longer than the timeout, it's deemed successful. 55 | // If the command runs within the timeout, it's deemed successful if it exited cleanly. 56 | func appearsSuccessful(cmd *exec.Cmd, timeout time.Duration) bool { 57 | errc := make(chan error, 1) 58 | go func() { 59 | errc <- cmd.Wait() 60 | }() 61 | 62 | select { 63 | case <-time.After(timeout): 64 | return true 65 | case err := <-errc: 66 | return err == nil 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /internal/content/telemetrygodev/static/index.min.css: -------------------------------------------------------------------------------- 1 | /* Code generated by esbuild. DO NOT EDIT. */ 2 | .js-Tree ul{list-style:none;padding-left:0}.js-Tree-item ul{display:none}.js-Tree-item{overflow:hidden;text-overflow:ellipsis;padding:.125rem 0}.js-Tree-item[aria-expanded=true] ul{display:block}.js-Tree-item .js-Tree-item{position:relative;padding-left:1.25rem}.js-Tree-item .js-Tree-item[aria-selected=true]:before{background-color:var(--color-brand-primary);border-radius:50%;content:"";display:block;height:.3125rem;left:.4688rem;position:absolute;top:.75rem;width:.3125rem}.js-Tree-item>a{color:var(--color-text-subtle);font-size:.875rem}.js-Tree-item[aria-selected=true]>a{color:var(--color-text)}svg g[aria-label=tip] g{fill:var(--color-background)}.Chartbrowser-view{display:flex;flex-direction:row}.Chartbrowser-index{flex:1 1;padding:0 1.5rem 0 0}.Chartbrowser-heading{font-weight:700;font-size:1.25rem;margin:0 0 .5rem}.Chartbrowser-index-sticky{position:sticky;top:1rem;width:10rem}.Chartbrowser-index-sticky>ul{position:sticky;top:1rem;margin-top:0}.Chartbrowser-link{color:var(--color-text-subtle);font-size:.875rem;line-height:1.5rem}.Chartbrowser-program{font-weight:400;margin:0 0 1rem}.Chartbrowser-program:not(:first-of-type){margin-top:2rem}.Chartbrowser-chart{background-color:var(--color-background);border:1px solid transparent;margin-bottom:1rem;padding:.875rem;box-shadow:0 1px 2px #3c40434d,0 1px 3px 1px #3c404326}.Chartbrowser-chart-name{text-align:center;margin:0}p{max-width:none}.Charts{margin-top:1rem}.Centered{display:flex;justify-content:center}.Charts-heading{margin:2.5rem 0 1.5rem}.Charts-heading h2{font-weight:400;margin:0}.Charts-heading a{border:var(--border);padding:.6rem;border-radius:.5rem;background-color:var(--color-background-highlighted-link)}.Charts-heading ul{margin:.5rem 0 0;list-style:none;display:inline-flex;padding:0}.Charts-heading li{list-style:none;display:inline-flex}.Charts-heading li:not(:last-child){margin-right:1rem} 3 | /*! 4 | * Copyright 2024 The Go Authors. All rights reserved. 5 | * Use of this source code is governed by a BSD-style 6 | * license that can be found in the LICENSE file. 7 | */ 8 | /*# sourceMappingURL=index.min.css.map */ 9 | -------------------------------------------------------------------------------- /internal/telemetry/proginfo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package telemetry 6 | 7 | import ( 8 | "go/version" 9 | "os" 10 | "path/filepath" 11 | "runtime/debug" 12 | "strings" 13 | ) 14 | 15 | // IsToolchainProgram reports whether a program with the given path is a Go 16 | // toolchain program. 17 | func IsToolchainProgram(progPath string) bool { 18 | return strings.HasPrefix(progPath, "cmd/") 19 | } 20 | 21 | // ProgramInfo extracts the go version, program package path, and program 22 | // version to use for counter files. 23 | // 24 | // For programs in the Go toolchain, the program version will be the same as 25 | // the Go version, and will typically be of the form "go1.2.3", not a semantic 26 | // version of the form "v1.2.3". Go versions may also include spaces and 27 | // special characters. 28 | func ProgramInfo(info *debug.BuildInfo) (goVers, progPath, progVers string) { 29 | goVers = info.GoVersion 30 | if strings.Contains(goVers, "devel") || strings.Contains(goVers, "-") || !version.IsValid(goVers) { 31 | goVers = "devel" 32 | } 33 | 34 | progPath = info.Path 35 | if progPath == "" { 36 | progPath = strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe") 37 | } 38 | 39 | // Main module version information is not populated for the cmd module, but 40 | // we can re-use the Go version here. 41 | if IsToolchainProgram(progPath) { 42 | progVers = goVers 43 | } else { 44 | progVers = info.Main.Version 45 | if strings.Contains(progVers, "devel") || strings.Count(progVers, "-") > 1 { 46 | // Heuristically mark all pseudo-version-like version strings as "devel" 47 | // to avoid creating too many counter files. 48 | // We should not use regexp that pulls in large dependencies. 49 | // Pseudo-versions have at least three parts (https://go.dev/ref/mod#pseudo-versions). 50 | // This heuristic still allows use to track prerelease 51 | // versions (e.g. gopls@v0.16.0-pre.1, vscgo@v0.42.0-rc.1). 52 | progVers = "devel" 53 | } 54 | } 55 | 56 | return goVers, progPath, progVers 57 | } 58 | -------------------------------------------------------------------------------- /internal/counter/parse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package counter 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "strings" 11 | "unsafe" 12 | 13 | "golang.org/x/telemetry/internal/mmap" 14 | ) 15 | 16 | type File struct { 17 | Meta map[string]string 18 | Count map[string]uint64 19 | } 20 | 21 | func Parse(filename string, data []byte) (*File, error) { 22 | if !bytes.HasPrefix(data, []byte(hdrPrefix)) || len(data) < pageSize { 23 | if len(data) < pageSize { 24 | return nil, fmt.Errorf("%s: file too short (%d<%d)", filename, len(data), pageSize) 25 | } 26 | return nil, fmt.Errorf("%s: wrong hdr (not %q)", filename, hdrPrefix) 27 | } 28 | corrupt := func() (*File, error) { 29 | // TODO(rfindley): return a useful error message. 30 | return nil, fmt.Errorf("%s: corrupt counter file", filename) 31 | } 32 | 33 | f := &File{ 34 | Meta: make(map[string]string), 35 | Count: make(map[string]uint64), 36 | } 37 | np := round(len(hdrPrefix), 4) 38 | hdrLen := *(*uint32)(unsafe.Pointer(&data[np])) 39 | if hdrLen > pageSize { 40 | return corrupt() 41 | } 42 | meta := data[np+4 : hdrLen] 43 | if i := bytes.IndexByte(meta, 0); i >= 0 { 44 | meta = meta[:i] 45 | } 46 | m := &mappedFile{ 47 | meta: string(meta), 48 | hdrLen: hdrLen, 49 | mapping: &mmap.Data{Data: data}, 50 | } 51 | 52 | lines := strings.Split(m.meta, "\n") 53 | for _, line := range lines { 54 | if line == "" { 55 | continue 56 | } 57 | k, v, ok := strings.Cut(line, ": ") 58 | if !ok { 59 | return corrupt() 60 | } 61 | f.Meta[k] = v 62 | } 63 | 64 | for i := uint32(0); i < numHash; i++ { 65 | headOff := hdrLen + hashOff + i*4 66 | head := m.load32(headOff) 67 | off := head 68 | for off != 0 { 69 | ename, next, v, ok := m.entryAt(off) 70 | if !ok { 71 | return corrupt() 72 | } 73 | if _, ok := f.Count[string(ename)]; ok { 74 | return corrupt() 75 | } 76 | ctrName := DecodeStack(string(ename)) 77 | f.Count[ctrName] = v.Load() 78 | off = next 79 | } 80 | } 81 | return f, nil 82 | } 83 | -------------------------------------------------------------------------------- /cmd/gotelemetry/help_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "go/format" 11 | "go/parser" 12 | "go/token" 13 | "os" 14 | "os/exec" 15 | "strings" 16 | "testing" 17 | 18 | "golang.org/x/telemetry/internal/testenv" 19 | ) 20 | 21 | var updateDocs = flag.Bool("update", false, "if set, update docs") 22 | 23 | func TestMain(m *testing.M) { 24 | if os.Getenv("GOTELEMETRY_RUN_AS_MAIN") != "" { 25 | main() 26 | os.Exit(0) 27 | } 28 | os.Exit(m.Run()) 29 | } 30 | 31 | func TestDocHelp(t *testing.T) { 32 | testenv.MustHaveExec(t) 33 | 34 | exe, err := os.Executable() 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | cmd := exec.Command(exe, "help") 39 | cmd.Env = append(os.Environ(), "GOTELEMETRY_RUN_AS_MAIN=1") 40 | help, err := cmd.Output() 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | if *updateDocs { 46 | var lines []string 47 | for _, line := range strings.Split(strings.TrimSpace(string(help)), "\n") { 48 | if len(line) > 0 { 49 | lines = append(lines, "// "+line) 50 | } else { 51 | lines = append(lines, "//") 52 | } 53 | } 54 | contents := fmt.Sprintf(`// Copyright 2023 The Go Authors. All rights reserved. 55 | // Use of this source code is governed by a BSD-style 56 | // license that can be found in the LICENSE file. 57 | 58 | // Code generated by TestDocHelp; DO NOT EDIT. 59 | 60 | %s 61 | package main 62 | `, strings.Join(lines, "\n")) 63 | 64 | data, err := format.Source([]byte(contents)) 65 | if err != nil { 66 | t.Fatalf("formatting content: %v", err) 67 | } 68 | 69 | if err := os.WriteFile("doc.go", data, 0666); err != nil { 70 | t.Fatalf("writing doc.go: %v", err) 71 | } 72 | } 73 | 74 | f, err := parser.ParseFile(token.NewFileSet(), "doc.go", nil, parser.PackageClauseOnly|parser.ParseComments) 75 | if err != nil { 76 | t.Fatalf("parsing doc.go: %v", err) 77 | } 78 | doc := f.Doc.Text() 79 | if got, want := doc, string(help); got != want { 80 | t.Errorf("doc.go: mismatching content\ngot:\n%s\nwant:\n%s", got, want) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /internal/unionfs/unionfs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package unionfs allows multiple file systems to be read as a union. 6 | package unionfs 7 | 8 | import ( 9 | "io/fs" 10 | ) 11 | 12 | var _ fs.ReadDirFS = FS{} 13 | 14 | // A FS is an FS presenting the union of the file systems in the slice. If 15 | // multiple file systems provide a particular file, Open uses the FS listed 16 | // earlier in the slice. 17 | type FS []fs.FS 18 | 19 | // Sub returns an FS corresponding to the merged subtree rooted at a set of 20 | // fsys's dirs. 21 | func Sub(fsys fs.FS, dirs ...string) (FS, error) { 22 | var subs FS 23 | for _, dir := range dirs { 24 | if _, err := fs.Stat(fsys, dir); err != nil { 25 | return nil, err 26 | } 27 | sub, err := fs.Sub(fsys, dir) 28 | if err != nil { 29 | return nil, err 30 | } 31 | subs = append(subs, sub) 32 | } 33 | return subs, nil 34 | } 35 | 36 | func (fsys FS) Open(name string) (fs.File, error) { 37 | var errOut error 38 | for _, sub := range fsys { 39 | f, err := sub.Open(name) 40 | if err == nil { 41 | return f, nil 42 | } 43 | if errOut == nil { 44 | errOut = err 45 | } 46 | } 47 | return nil, errOut 48 | } 49 | 50 | func (fsys FS) ReadDir(name string) ([]fs.DirEntry, error) { 51 | var all []fs.DirEntry 52 | var seen map[string]bool // seen[name] is true if name is listed in all; lazily initialized 53 | var errOut error 54 | for _, sub := range fsys { 55 | list, err := fs.ReadDir(sub, name) 56 | if err != nil { 57 | errOut = err 58 | } 59 | if len(all) == 0 { 60 | all = append(all, list...) 61 | } else { 62 | if seen == nil { 63 | // Initialize seen only after we get two different directory listings. 64 | seen = make(map[string]bool) 65 | for _, d := range all { 66 | seen[d.Name()] = true 67 | } 68 | } 69 | for _, d := range list { 70 | name := d.Name() 71 | if !seen[name] { 72 | seen[name] = true 73 | all = append(all, d) 74 | } 75 | } 76 | } 77 | } 78 | if len(all) > 0 { 79 | return all, nil 80 | } 81 | return nil, errOut 82 | } 83 | -------------------------------------------------------------------------------- /counter/countertest/countertest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // countertest provides testing utilities for counters. 6 | // This package cannot be used except for testing. 7 | package countertest 8 | 9 | import ( 10 | "sync" 11 | "testing" 12 | 13 | "golang.org/x/telemetry/counter" 14 | ic "golang.org/x/telemetry/internal/counter" 15 | "golang.org/x/telemetry/internal/telemetry" 16 | ) 17 | 18 | var ( 19 | openedMu sync.Mutex 20 | opened bool 21 | ) 22 | 23 | // SupportedPlatform reports if this platform supports Open() 24 | const SupportedPlatform = !telemetry.DisabledOnPlatform 25 | 26 | func isOpen() bool { 27 | openedMu.Lock() 28 | defer openedMu.Unlock() 29 | return opened 30 | } 31 | 32 | // Open enables telemetry data writing to disk. 33 | // This is supposed to be called once during the program execution 34 | // (i.e. typically in TestMain), and must not be used with 35 | // golang.org/x/telemetry/counter.Open. 36 | func Open(telemetryDir string) { 37 | openedMu.Lock() 38 | defer openedMu.Unlock() 39 | if opened { 40 | panic("Open was called more than once") 41 | } 42 | telemetry.Default = telemetry.NewDir(telemetryDir) 43 | 44 | // TODO(rfindley): reinstate test coverage with counter rotation enabled. 45 | // Before the [counter.Open] and [counter.OpenAndRotate] APIs were split, 46 | // this called counter.Open (which rotated!). 47 | counter.Open() 48 | opened = true 49 | } 50 | 51 | // ReadCounter reads the given counter. 52 | func ReadCounter(c *counter.Counter) (count uint64, _ error) { 53 | return ic.Read(c) 54 | } 55 | 56 | // ReadStackCounter reads the given StackCounter. 57 | func ReadStackCounter(c *counter.StackCounter) (stackCounts map[string]uint64, _ error) { 58 | return ic.ReadStack(c) 59 | } 60 | 61 | // ReadFile reads the counters and stack counters from the given file. 62 | func ReadFile(name string) (counters, stackCounters map[string]uint64, _ error) { 63 | return ic.ReadFile(name) 64 | } 65 | 66 | func init() { 67 | // Extra safety check. 68 | if !testing.Testing() { 69 | panic("use of this package is disallowed in non-testing code") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/configstore/download_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package configstore_test 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | "reflect" 11 | "strings" 12 | "testing" 13 | 14 | "golang.org/x/telemetry/internal/configstore" 15 | "golang.org/x/telemetry/internal/configtest" 16 | "golang.org/x/telemetry/internal/telemetry" 17 | "golang.org/x/telemetry/internal/testenv" 18 | ) 19 | 20 | func TestDownload(t *testing.T) { 21 | testenv.NeedsGo(t) 22 | 23 | configVersion := "v0.1.0" 24 | in := &telemetry.UploadConfig{ 25 | GOOS: []string{"darwin"}, 26 | GOARCH: []string{"amd64", "arm64"}, 27 | GoVersion: []string{"1.20.3", "1.20.4"}, 28 | Programs: []*telemetry.ProgramConfig{{ 29 | Name: "gopls", 30 | Versions: []string{"v0.11.0"}, 31 | Counters: []telemetry.CounterConfig{{ 32 | Name: "foobar", 33 | Rate: 2, 34 | }}, 35 | }}, 36 | } 37 | 38 | env := configtest.LocalProxyEnv(t, in, configVersion) 39 | testCases := []struct { 40 | version string 41 | want telemetry.UploadConfig 42 | }{ 43 | {version: configVersion, want: *in}, 44 | {version: "latest", want: *in}, 45 | } 46 | for _, tc := range testCases { 47 | t.Run(tc.version, func(t *testing.T) { 48 | got, _, err := configstore.Download(tc.version, env) 49 | if err != nil { 50 | t.Fatal("failed to download:", err) 51 | } 52 | 53 | want := tc.want 54 | if !reflect.DeepEqual(*got, want) { 55 | t.Errorf("Download(latest, _) = %v\nwant %v", stringify(got), stringify(want)) 56 | } 57 | }) 58 | } 59 | 60 | t.Run("invalidversion", func(t *testing.T) { 61 | got, ver, err := configstore.Download("nonexisting", env) 62 | if err == nil { 63 | t.Fatalf("download succeeded unexpectedly: %v %+v", ver, got) 64 | } 65 | if !strings.Contains(err.Error(), "invalid version") { 66 | t.Errorf("unexpected error message: %v", err) 67 | } 68 | }) 69 | } 70 | 71 | func stringify(x any) string { 72 | ret, err := json.MarshalIndent(x, "", " ") 73 | if err != nil { 74 | return fmt.Sprintf("json.Marshal failed - %v", err) 75 | } 76 | return string(ret) 77 | } 78 | -------------------------------------------------------------------------------- /godev/cmd/telemetrygodev/deploy-prod.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Go Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | steps: 6 | # Build the container image 7 | - name: "gcr.io/cloud-builders/docker" 8 | args: 9 | - "build" 10 | - "-t" 11 | - "gcr.io/$PROJECT_ID/telemetrygodev:$COMMIT_SHA" 12 | - "-f" 13 | - "godev/cmd/telemetrygodev/Dockerfile" 14 | - "." 15 | # Push the container image to Container Registry 16 | - name: "gcr.io/cloud-builders/docker" 17 | args: 18 | - "push" 19 | - "gcr.io/$PROJECT_ID/telemetrygodev:$COMMIT_SHA" 20 | # Acquire the deployment lock 21 | - name: golang 22 | args: 23 | - "go" 24 | - "run" 25 | - "golang.org/x/website/cmd/locktrigger@latest" 26 | - "--project=$PROJECT_ID" 27 | - "--build=$BUILD_ID" 28 | - "--repo=https://go.googlesource.com/telemetry" 29 | # Deploy container image to dev Cloud Run service 30 | - name: "gcr.io/google.com/cloudsdktool/cloud-sdk" 31 | entrypoint: gcloud 32 | args: 33 | - "run" 34 | - "deploy" 35 | - "dev-telemetry" 36 | - "--image" 37 | - "gcr.io/$PROJECT_ID/telemetrygodev:$COMMIT_SHA" 38 | - "--region" 39 | - "us-central1" 40 | - "--service-account" 41 | - "$_RUN_SERVICE_ACCOUNT" 42 | - "--set-env-vars" 43 | - "GO_TELEMETRY_PROJECT_ID=$PROJECT_ID,GO_TELEMETRY_ENV=dev" 44 | # Run push tests 45 | - name: "golang" 46 | entrypoint: sh 47 | dir: "godev" 48 | args: 49 | - "-c" 50 | - "go test ./cmd/telemetrygodev -run=TestPaths -telemetry_url=https://dev-telemetry.go.dev" 51 | # Deploy container image to prod Cloud Run service 52 | - name: "gcr.io/google.com/cloudsdktool/cloud-sdk" 53 | entrypoint: gcloud 54 | args: 55 | - "run" 56 | - "deploy" 57 | - "prod-telemetry" 58 | - "--image" 59 | - "gcr.io/$PROJECT_ID/telemetrygodev:$COMMIT_SHA" 60 | - "--region" 61 | - "us-central1" 62 | - "--service-account" 63 | - "$_RUN_SERVICE_ACCOUNT" 64 | - "--set-env-vars" 65 | - "GO_TELEMETRY_PROJECT_ID=$PROJECT_ID,GO_TELEMETRY_ENV=prod" 66 | images: 67 | - "gcr.io/$PROJECT_ID/telemetrygodev:$COMMIT_SHA" 68 | -------------------------------------------------------------------------------- /godev/cmd/telemetrygodev/README.md: -------------------------------------------------------------------------------- 1 | # telemetrygodev 2 | 3 | ## Local Development 4 | 5 | For local development, simply build and run. It serves on localhost:8080. 6 | 7 | go run ./godev/cmd/telemetrygodev 8 | 9 | By default, the server will use the filesystem for storage object I/O. Use the 10 | -gcs flag to use the Cloud Storage API. 11 | 12 | go run ./godev/cmd/worker --gcs 13 | 14 | Optionally, use the localstorage devtool the emulate the GCS server on your machine. 15 | 16 | ./godev/devtools/localstorage.sh 17 | STORAGE_EMULATOR_HOST=localhost:8081 go run ./godev/cmd/worker --gcs 18 | 19 | ### Environment Variables 20 | 21 | | Name | Default | Description | 22 | | ---------------------------------- | --------------------- | --------------------------------------------------------- | 23 | | GO_TELEMETRY_PROJECT_ID | go-telemetry | GCP project ID | 24 | | GO_TELEMETRY_LOCAL_STORAGE | .localstorage | Directory for storage emulator I/O or file system storage | 25 | | GO_TELEMETRY_UPLOAD_CONFIG | ../config/config.json | Location of the upload config used for report validation | 26 | | GO_TELEMETRY_MAX_REQUEST_BYTES | 102400 | Maximum request body size the server allows | 27 | | GO_TELEMETRY_ENV | local | Deployment environment (e.g. prod, dev, local, ... ) | 28 | 29 | ## Testing 30 | 31 | The telemetry.go.dev web site has a suite of regression tests that can be run 32 | with: 33 | 34 | go test golang.org/x/telemetry/... 35 | 36 | ## Deploying 37 | 38 | Each time a CL is reviewed and submitted, the site is automatically deployed to 39 | Cloud Run. If it passes its serving-readiness checks, it will be automatically 40 | promoted to handle traffic. 41 | 42 | If the automatic deployment is not working, or to check on the status of a 43 | pending deployment, see the “telemetrygodev” trigger in the 44 | [Cloud Build history](https://pantheon.corp.google.com/cloud-build/builds?project=go-telemetry). 45 | 46 | ### Test Instance 47 | 48 | To deploy a test instance of this service, push to a branch and manually trigger 49 | the deploy job from the 50 | [Cloud Build console](https://pantheon.corp.google.com/cloud-build/triggers?project=go-telemetry) 51 | with the desired values for branch and service name. 52 | -------------------------------------------------------------------------------- /counter/countertest/countertest_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package countertest 6 | 7 | import ( 8 | "fmt" 9 | "slices" 10 | "strings" 11 | "sync" 12 | "testing" 13 | 14 | "golang.org/x/telemetry/counter" 15 | "golang.org/x/telemetry/internal/telemetry" 16 | "golang.org/x/telemetry/internal/testenv" 17 | ) 18 | 19 | func TestReadCounter(t *testing.T) { 20 | testenv.SkipIfUnsupportedPlatform(t) 21 | c := counter.New("foobar") 22 | 23 | got, err := ReadCounter(c) 24 | if err != nil { 25 | t.Errorf("ReadCounter = (%d, %v), want (0,nil)", got, err) 26 | } 27 | if got != 0 { 28 | t.Fatalf("ReadCounter = %d, want 0", got) 29 | } 30 | 31 | var wg sync.WaitGroup 32 | wg.Add(100) 33 | for i := 0; i < 100; i++ { 34 | go func() { 35 | c.Inc() 36 | wg.Done() 37 | }() 38 | } 39 | wg.Wait() 40 | if got, err := ReadCounter(c); err != nil || got != 100 { 41 | t.Errorf("ReadCounter = (%v, %v), want (%v, nil)", got, err, 100) 42 | } 43 | } 44 | 45 | func TestReadStackCounter(t *testing.T) { 46 | testenv.SkipIfUnsupportedPlatform(t) 47 | c := counter.NewStack("foobar", 8) 48 | 49 | if got, err := ReadStackCounter(c); err != nil || len(got) != 0 { 50 | t.Errorf("ReadStackCounter = (%#v, %v), want (map with zero elements, nil)", got, err) 51 | } 52 | 53 | var wg sync.WaitGroup 54 | wg.Add(100) 55 | for i := 0; i < 100; i++ { 56 | go func() { 57 | c.Inc() // one stack! 58 | wg.Done() 59 | }() 60 | } 61 | wg.Wait() 62 | 63 | got, err := ReadStackCounter(c) 64 | if err != nil || len(got) != 1 { 65 | t.Fatalf("ReadStackCounter = (%v, %v), want to read one entry", stringify(got), err) 66 | } 67 | for k, v := range got { 68 | if !strings.Contains(k, t.Name()) || v != 100 { 69 | t.Fatalf("ReadStackCounter = %v, want a stack counter with value 100", got) 70 | } 71 | } 72 | } 73 | 74 | func TestSupport(t *testing.T) { 75 | if SupportedPlatform == telemetry.DisabledOnPlatform { 76 | t.Errorf("supported mismatch: us %v, telemetry.internal.Disabled %v", 77 | SupportedPlatform, telemetry.DisabledOnPlatform) 78 | } 79 | } 80 | 81 | func stringify(m map[string]uint64) string { 82 | kv := make([]string, 0, len(m)) 83 | for k, v := range m { 84 | kv = append(kv, fmt.Sprintf("%q:%v", k, v)) 85 | } 86 | slices.Sort(kv) 87 | return "{" + strings.Join(kv, " ") + "}" 88 | } 89 | -------------------------------------------------------------------------------- /internal/upload/date.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package upload 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "sync" 11 | "time" 12 | 13 | "golang.org/x/telemetry/internal/counter" 14 | "golang.org/x/telemetry/internal/telemetry" 15 | ) 16 | 17 | // time and date handling 18 | 19 | var distantPast = 21 * 24 * time.Hour 20 | 21 | // reports that are too old (21 days) are not uploaded 22 | func (u *uploader) tooOld(date string, uploadStartTime time.Time) bool { 23 | t, err := time.Parse(telemetry.DateOnly, date) 24 | if err != nil { 25 | u.logger.Printf("tooOld: %v", err) 26 | return false 27 | } 28 | age := uploadStartTime.Sub(t) 29 | return age > distantPast 30 | } 31 | 32 | // counterDateSpan parses the counter file named fname and returns the (begin, 33 | // end) span recorded in its metadata, or an error if this data could not be 34 | // extracted. 35 | func (u *uploader) counterDateSpan(fname string) (begin, end time.Time, _ error) { 36 | parsed, err := u.parseCountFile(fname) 37 | if err != nil { 38 | return time.Time{}, time.Time{}, err 39 | } 40 | timeBegin, ok := parsed.Meta["TimeBegin"] 41 | if !ok { 42 | return time.Time{}, time.Time{}, fmt.Errorf("missing counter metadata for TimeBegin") 43 | } 44 | begin, err = time.Parse(time.RFC3339, timeBegin) 45 | if err != nil { 46 | return time.Time{}, time.Time{}, fmt.Errorf("failed to parse TimeBegin: %v", err) 47 | } 48 | timeEnd, ok := parsed.Meta["TimeEnd"] 49 | if !ok { 50 | return time.Time{}, time.Time{}, fmt.Errorf("missing counter metadata for TimeEnd") 51 | } 52 | end, err = time.Parse(time.RFC3339, timeEnd) 53 | if err != nil { 54 | return time.Time{}, time.Time{}, fmt.Errorf("failed to parse TimeEnd: %v", err) 55 | } 56 | return begin, end, nil 57 | } 58 | 59 | // avoid parsing count files multiple times 60 | type parsedCache struct { 61 | mu sync.Mutex 62 | m map[string]*counter.File 63 | } 64 | 65 | func (u *uploader) parseCountFile(fname string) (*counter.File, error) { 66 | u.cache.mu.Lock() 67 | defer u.cache.mu.Unlock() 68 | if u.cache.m == nil { 69 | u.cache.m = make(map[string]*counter.File) 70 | } 71 | if f, ok := u.cache.m[fname]; ok { 72 | return f, nil 73 | } 74 | buf, err := os.ReadFile(fname) 75 | if err != nil { 76 | return nil, fmt.Errorf("parse ReadFile: %v for %s", err, fname) 77 | } 78 | f, err := counter.Parse(fname, buf) 79 | if err != nil { 80 | 81 | return nil, fmt.Errorf("parse Parse: %v for %s", err, fname) 82 | } 83 | u.cache.m[fname] = f 84 | return f, nil 85 | } 86 | -------------------------------------------------------------------------------- /internal/unionfs/unionfs_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package unionfs 6 | 7 | import ( 8 | "io" 9 | "os" 10 | "reflect" 11 | "testing" 12 | ) 13 | 14 | func TestFS_Open(t *testing.T) { 15 | fsys, err := Sub(os.DirFS("testdata"), "dir1", "dir2") 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | 20 | type args struct { 21 | name string 22 | } 23 | tests := []struct { 24 | name string 25 | args args 26 | want string 27 | wantErr bool 28 | }{ 29 | { 30 | name: "file1 from dir1", 31 | args: args{ 32 | name: "file1", 33 | }, 34 | want: "file 1 content from dir 1\n", 35 | wantErr: false, 36 | }, 37 | { 38 | name: "file2 from dir2", 39 | args: args{ 40 | name: "file2", 41 | }, 42 | want: "file 2 content\n", 43 | wantErr: false, 44 | }, 45 | { 46 | name: "file not found", 47 | args: args{ 48 | name: "file3", 49 | }, 50 | want: "file3", 51 | wantErr: true, 52 | }, 53 | } 54 | for _, tt := range tests { 55 | t.Run(tt.name, func(t *testing.T) { 56 | file, err := fsys.Open(tt.args.name) 57 | if (err != nil) != tt.wantErr { 58 | t.Errorf("FS.Open() error = %v, wantErr %v", err, tt.wantErr) 59 | return 60 | } 61 | if !tt.wantErr { 62 | bytes, err := io.ReadAll(file) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | got := string(bytes) 67 | if got != tt.want { 68 | t.Errorf("FS.Open() = %v, want %v", got, tt.want) 69 | } 70 | } 71 | }) 72 | } 73 | } 74 | 75 | func TestFS_ReadDir(t *testing.T) { 76 | var err error 77 | fsys, err := Sub(os.DirFS("testdata"), "dir1", "dir2") 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | type args struct { 82 | name string 83 | } 84 | tests := []struct { 85 | name string 86 | args args 87 | fsys FS 88 | wantFiles []string 89 | }{ 90 | { 91 | name: "", 92 | args: args{"."}, 93 | fsys: fsys, 94 | wantFiles: []string{"file1", "file2"}, 95 | }, 96 | } 97 | for _, tt := range tests { 98 | t.Run(tt.name, func(t *testing.T) { 99 | dirs, err := tt.fsys.ReadDir(tt.args.name) 100 | if err != nil { 101 | t.Errorf("FS.ReadDir() error = %v", err) 102 | return 103 | } 104 | var got []string 105 | for _, v := range dirs { 106 | got = append(got, v.Name()) 107 | } 108 | if !reflect.DeepEqual(got, tt.wantFiles) { 109 | t.Errorf("FS.ReadDir() = %v, want %v", got, tt.wantFiles) 110 | } 111 | }) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /internal/upload/Doc.txt: -------------------------------------------------------------------------------- 1 | The upload process converts count files into reports, and 2 | uploads reports. There will be only one report, named YYYY-MM-DD.json, 3 | for a given day. 4 | 5 | First phase. Look at the localdir (os.UserConfigdir()/go/telemetry/local) 6 | and find all .count and .json files. Find the count files that are no 7 | longer active by looking at their metadata. 8 | 9 | Second phase. Group the inactive count files by their expiry date, and 10 | for each date generate the local report and the upload report. (The upload 11 | report only contains the counters in the upload configuration.) The upload 12 | report is saved in the local directory with a name like YYYY-MM-DD.json, if 13 | there is no file already existing with that name. 14 | If the local report is different, it is saved in the local directory 15 | with a name like local.YYYY-MM-DD.json. The new upload report is 16 | added to the list of .json files from the first phase. At this point 17 | the count files are no longer needed and can be deleted. 18 | 19 | Third phase. Look at the .json files in the list from the first phase. 20 | If the name starts with local, skip it. If there is a file with the 21 | identical name in the upload directory, remove the one in the local directory. 22 | Otherwise try to upload the one in the local directory, 23 | If the upload succeeds, move the file to the uploaded directory. 24 | 25 | 26 | There are various error conditions. 27 | 1. Several processes could look at localdir and see work to do. 28 | 1A. They could see different sets of expired count files for some day. 29 | This could happen if another process is removing count files. In this 30 | case there is already a YYYY-MM-DD.json file either in localdir 31 | or updatedir, so the process seeing fewer count files will not generate 32 | a report. 33 | 1B. They could see the same count files, and no report in either directory. 34 | They will both generate (in memory) reports and check to see if there 35 | is a YYYY-MM-DD.json file in either directory. They could both then 36 | write two files with the same name, but different X values, but 37 | otherwise the same contents. The X values are very close to the front 38 | of the file. Assuming reasonable file system semantics one version of 39 | the file will be written. To minimize this, just before writing reports 40 | the code checks again to see if they exist. 41 | 1C. Once there is an existing well-formed file YYYY-MM-DD.json in localdir 42 | eventually the upload will succeed, and the file will be moved to updatedir. 43 | It is possible that other processes will not see the file in updatedir and 44 | upload it again and also move it to uploaddir. This is harmless as all 45 | the uploaded files are identical. 46 | -------------------------------------------------------------------------------- /internal/content/shared/static/chartbrowser.min.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../treenav.css", "../chartbrowser.css"], 4 | "sourcesContent": ["/*!\n * Copyright 2024 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n.js-Tree ul {\n list-style: none;\n padding-left: 0;\n}\n\n.js-Tree-item ul {\n display: none;\n}\n\n.js-Tree-item {\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 0.125rem 0 0.125rem 0;\n}\n\n.js-Tree-item[aria-expanded='true'] ul {\n display: block;\n}\n\n.js-Tree-item .js-Tree-item {\n position: relative;\n padding-left: 1.25rem;\n}\n\n.js-Tree-item .js-Tree-item[aria-selected='true']:before {\n background-color: var(--color-brand-primary);\n border-radius: 50%;\n content: \"\";\n display: block;\n height: .3125rem;\n left: .4688rem;\n position: absolute;\n top: .75rem;\n width: .3125rem;\n}\n\n.js-Tree-item>a {\n color: var(--color-text-subtle);\n font-size: .875rem;\n}\n\n.js-Tree-item[aria-selected='true']>a {\n color: var(--color-text);\n}\n\n", "/*!\n * Copyright 2024 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n@import url(\"../shared/treenav.css\");\n\n/* Fix tooltip background for dark theme */\nsvg g[aria-label=\"tip\"] g {\n fill: var(--color-background);\n}\n\n.Chartbrowser-view {\n display: flex;\n flex-direction: row;\n}\n.Chartbrowser-index {\n flex: 1 1;\n padding: 0 1.5rem 0 0;\n}\n.Chartbrowser-heading {\n font-weight: bold;\n font-size: 1.25rem;\n margin: 0 0 0.5rem 0;\n}\n.Chartbrowser-index-sticky {\n position: sticky;\n top: 1rem;\n width: 10rem;\n}\n.Chartbrowser-index-sticky > ul {\n position: sticky;\n top: 1rem;\n margin-top: 0;\n}\n.Chartbrowser-link {\n color: var(--color-text-subtle);\n font-size: .875rem;\n line-height: 1.5rem;\n}\n.Chartbrowser-program {\n font-weight: normal;\n margin: 0 0 1rem 0;\n}\n.Chartbrowser-program:not(:first-of-type) {\n margin-top: 2rem;\n}\n.Chartbrowser-chart {\n background-color: var(--color-background);\n border: 1px solid transparent;\n margin-bottom: 1rem;\n padding: 0.875rem;\n box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .3), 0 1px 3px 1px rgba(60, 64, 67, .15);\n}\n.Chartbrowser-chart-name {\n text-align: center;\n margin: 0;\n}\n"], 5 | "mappings": ";AAMA,YACE,gBACA,eAGF,iBACE,aAGF,cACE,gBACA,uBAjBF,kBAqBA,qCACE,cAGF,4BACE,kBACA,qBAGF,uDACE,4CA/BF,kBAiCE,WACA,cACA,gBACA,cACA,kBACA,WACA,eAGF,gBACE,+BACA,kBAGF,oCACE,wBCvCF,wBACE,6BAGF,mBACE,aACA,mBAEF,oBACE,SAlBF,qBAqBA,sBACE,gBACA,kBAvBF,iBA0BA,2BACE,gBACA,SACA,YAEF,8BACE,gBACA,SACA,aAEF,mBACE,+BACA,kBACA,mBAEF,sBACE,gBA1CF,gBA6CA,0CACE,gBAEF,oBACE,yCACA,6BACA,mBAnDF,gBAqDE,uDAEF,yBACE,kBAxDF", 6 | "names": [] 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Telemetry 2 | 3 | This repository holds the Go Telemetry server code and libraries, used for 4 | hosting [telemetry.go.dev](https://telemetry.go.dev) and instrumenting Go 5 | toolchain programs with opt-in telemetry. 6 | 7 | **Warning**: this repository is intended for use only in tools maintained by 8 | the Go team, including tools in the Go distribution and auxiliary tools like 9 | [gopls](https://pkg.go.dev/golang.org/x/tools/gopls) or 10 | [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck). There are 11 | no compatibility guarantees for any of the packages here: public APIs will 12 | change in breaking ways as the telemetry integration is refined. 13 | 14 | ## Notable Packages 15 | 16 | - The [x/telemetry/counter](https://pkg.go.dev/golang.org/x/telemetry/counter) 17 | package provides a library for instrumenting programs with counters and stack 18 | reports. 19 | - The [x/telemetry/upload](https://pkg.go.dev/golang.org/x/telemetry/upload) 20 | package provides a hook for Go toolchain programs to upload telemetry data, 21 | if the user has opted in to telemetry uploading. 22 | - The [x/telemetry/cmd/gotelemetry](https://pkg.go.dev/pkg/golang.org/x/telemetry/cmd/gotelemetry) 23 | command is used for managing telemetry data and configuration. 24 | - The [x/telemetry/config](https://pkg.go.dev/pkg/golang.org/x/telemetry/config) 25 | package defines the subset of telemetry data that has been approved for 26 | uploading by the telemetry proposal process. 27 | - The [x/telemetry/godev](https://pkg.go.dev/pkg/golang.org/x/telemetry/godev) directory defines 28 | the services running at [telemetry.go.dev](https://telemetry.go.dev). 29 | 30 | ## Contributing 31 | 32 | This repository uses Gerrit for code changes. To learn how to submit changes to 33 | this repository, see https://go.dev/doc/contribute. 34 | 35 | The git repository is https://go.googlesource.com/telemetry. 36 | 37 | The main issue tracker for the telemetry repository is located at 38 | https://go.dev/issues. Prefix your issue with "x/telemetry:" in 39 | the subject line, so it is easy to find. 40 | 41 | ### Linting & Formatting 42 | 43 | This repository uses [eslint](https://eslint.org/) to format TS files, 44 | [stylelint](https://stylelint.io/) to format CSS files, and 45 | [prettier](https://prettier.io/) to format TS, CSS, Markdown, and YAML files. 46 | 47 | See the style guides: 48 | 49 | - [TypeScript](https://google.github.io/styleguide/tsguide.html) 50 | - [CSS](https://go.dev/wiki/CSSStyleGuide) 51 | 52 | It is encouraged that all TS and CSS code be run through formatters before 53 | submitting a change. However, it is not a strict requirement enforced by CI. 54 | 55 | ### Installing npm Dependencies: 56 | 57 | 1. Install [docker](https://docs.docker.com/get-docker/) 58 | 2. Run `./npm install` 59 | 60 | ### Run ESLint, Stylelint, & Prettier 61 | 62 | ./npm run all 63 | -------------------------------------------------------------------------------- /internal/config/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package config 6 | 7 | import ( 8 | _ "embed" 9 | "encoding/json" 10 | "os" 11 | "path/filepath" 12 | "testing" 13 | 14 | "golang.org/x/telemetry/internal/telemetry" 15 | ) 16 | 17 | func TestConfig(t *testing.T) { 18 | f, err := os.Open(filepath.FromSlash("../../config/config.json")) 19 | if os.IsNotExist(err) { 20 | t.Skip("config file not found") 21 | } 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | defer f.Close() 26 | var cfg telemetry.UploadConfig 27 | d := json.NewDecoder(f) 28 | d.DisallowUnknownFields() 29 | if err := d.Decode(&cfg); err != nil { 30 | t.Fatal(err) 31 | } 32 | } 33 | 34 | func TestInternalConfig(t *testing.T) { 35 | got, err := ReadConfig("testdata/config.json") 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | wantGOOS := []string{"linux", "darwin"} 40 | wantGOARCH := []string{"amd64", "arm64"} 41 | wantGoVersion := []string{"go1.20", "go1.20.1"} 42 | wantPrograms := []string{"golang.org/x/tools/gopls", "cmd/go"} 43 | wantVersions := [][2]string{ 44 | {"golang.org/x/tools/gopls", "v0.10.1"}, 45 | {"golang.org/x/tools/gopls", "v0.11.0"}, 46 | } 47 | wantCounters := [][2]string{ 48 | {"golang.org/x/tools/gopls", "editor:emacs"}, 49 | {"golang.org/x/tools/gopls", "editor:vim"}, 50 | {"golang.org/x/tools/gopls", "editor:vscode"}, 51 | {"golang.org/x/tools/gopls", "editor:other"}, 52 | {"cmd/go", "go/buildcache/miss:0"}, 53 | {"cmd/go", "go/buildcache/miss:1"}, 54 | {"cmd/go", "go/buildcache/miss:10"}, 55 | {"cmd/go", "go/buildcache/miss:100"}, 56 | } 57 | wantPrefix := [][2]string{ 58 | {"golang.org/x/tools/gopls", "editor"}, 59 | {"cmd/go", "go/buildcache/miss"}, 60 | } 61 | 62 | for _, w := range wantGOOS { 63 | if !got.HasGOOS(w) { 64 | t.Errorf("got.HasGOOS(%s) = false: want true", w) 65 | } 66 | } 67 | for _, w := range wantGOARCH { 68 | if !got.HasGOARCH(w) { 69 | t.Errorf("got.HasGOARCH(%s) = false: want true", w) 70 | } 71 | } 72 | for _, w := range wantGoVersion { 73 | if !got.HasGoVersion(w) { 74 | t.Errorf("got.HasGoVersion(%s) = false: want true", w) 75 | } 76 | } 77 | for _, w := range wantPrograms { 78 | if !got.HasProgram(w) { 79 | t.Errorf("got.HasProgram(%s) = false: want true", w) 80 | } 81 | } 82 | for _, w := range wantVersions { 83 | if !got.HasVersion(w[0], w[1]) { 84 | t.Errorf("got.HasVersion(%s, %s) = false: want true", w[0], w) 85 | } 86 | } 87 | for _, w := range wantCounters { 88 | if !got.HasCounter(w[0], w[1]) { 89 | t.Errorf("got.HasCounter(%s, %s) = false: want true", w[0], w[1]) 90 | } 91 | } 92 | for _, w := range wantPrefix { 93 | if !got.HasCounterPrefix(w[0], w[1]) { 94 | t.Errorf("got.HasCounterPrefix(%s, %s) = false: want true", w[0], w[1]) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /counter/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package counter implements a simple counter system for collecting 6 | // totally public telemetry data. 7 | // 8 | // There are two kinds of counters, basic counters and stack counters. 9 | // Basic counters are created by [New]. 10 | // Stack counters are created by [NewStack]. 11 | // Both are incremented by calling Inc(). 12 | // 13 | // Basic counters are very cheap. Stack counters are more expensive, as they 14 | // require parsing the stack. (Stack counters are implemented as basic counters 15 | // whose names are the concatenation of the name and the stack trace. There is 16 | // an upper limit on the size of this name, about 4K bytes. If the name is too 17 | // long the stack will be truncated and "truncated" appended.) 18 | // 19 | // When counter files expire they are turned into reports by the upload 20 | // package. The first time any counter file is created for a user, a random day 21 | // of the week is selected on which counter files will expire. For the first 22 | // week, that day is more than 7 days (but not more than two weeks) in the 23 | // future. After that the counter files expire weekly on the same day of the 24 | // week. 25 | // 26 | // # Counter Naming 27 | // 28 | // Counter names passed to [New] and [NewStack] should follow these 29 | // conventions: 30 | // 31 | // - Names cannot contain whitespace or newlines. 32 | // 33 | // - Names must be valid unicode, with no unprintable characters. 34 | // 35 | // - Names may contain at most one ':'. In the counter "foo:bar", we refer to 36 | // "foo" as the "chart name" and "bar" as the "bucket name". 37 | // 38 | // - The '/' character should partition counter names into a hierarchy. The 39 | // root of this hierarchy should identify the logical entity that "owns" 40 | // the counter. This could be an application, such as "gopls" in the case 41 | // of "gopls/client:vscode", or a shared library, such as "crash" in the 42 | // case of the "crash/crash" counter owned by the crashmonitor library. If 43 | // the entity name itself contains a '/', that's ok: "cmd/go/flag" is fine. 44 | // 45 | // - Words should be '-' separated, as in "gopls/completion/errors-latency" 46 | // 47 | // - Histograms should use bucket names identifying upper bounds with '<'. 48 | // For example given two counters "gopls/completion/latency:<50ms" and 49 | // "gopls/completion/latency:<100ms", the "<100ms" bucket counts events 50 | // with latency in the half-open interval [50ms, 100ms). 51 | // 52 | // # Debugging 53 | // 54 | // The GODEBUG environment variable can enable printing of additional debug 55 | // information for counters. Adding GODEBUG=countertrace=1 to the environment 56 | // of a process using counters causes the x/telemetry/counter package to log 57 | // counter information to stderr. 58 | package counter 59 | -------------------------------------------------------------------------------- /internal/testenv/testenv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package testenv contains helper functions for skipping tests 6 | // based on which tools are present in the environment. 7 | package testenv 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | "go/build" 13 | "os/exec" 14 | "runtime" 15 | "sync" 16 | "testing" 17 | 18 | "golang.org/x/telemetry/internal/telemetry" 19 | ) 20 | 21 | // NeedsLocalhostNet skips t if networking does not work for ports opened 22 | // with "localhost". 23 | func NeedsLocalhostNet(t testing.TB) { 24 | switch runtime.GOOS { 25 | case "js", "wasip1": 26 | t.Skipf(`Listening on "localhost" fails on %s; see https://go.dev/issue/59718`, runtime.GOOS) 27 | } 28 | } 29 | 30 | var ( 31 | hasGoOnce sync.Once 32 | hasGoErr error 33 | ) 34 | 35 | // HasGo checks whether the current system has 'go'. 36 | func HasGo() error { 37 | hasGoOnce.Do(func() { 38 | cmd := exec.Command("go", "env", "GOROOT") 39 | out, err := cmd.Output() 40 | if err != nil { // cannot run go. 41 | hasGoErr = fmt.Errorf("%v: %v", cmd, err) 42 | return 43 | } 44 | out = bytes.TrimSpace(out) 45 | if len(out) == 0 { // unusual, incomplete go installation. 46 | hasGoErr = fmt.Errorf("%v: no GOROOT - incomplete go installation", cmd) 47 | } 48 | }) 49 | return hasGoErr 50 | } 51 | 52 | // NeedsGo skips t if the current system does not have 'go' or 53 | // cannot run them with exec.Command. 54 | func NeedsGo(t testing.TB) { 55 | if err := HasGo(); err != nil { 56 | t.Skipf("skipping test: go is not available - %v", err) 57 | } 58 | } 59 | 60 | // SkipIfUnsupportedPlatform skips test if the current os/arch 61 | // are not support. 62 | func SkipIfUnsupportedPlatform(t testing.TB) { 63 | t.Helper() 64 | if telemetry.DisabledOnPlatform { 65 | t.Skip("telemetry is unsupported on this platform") 66 | } 67 | } 68 | 69 | // MustHaveExec checks that the current system can start new processes 70 | // using os.StartProcess or (more commonly) exec.Command. 71 | // If not, MustHaveExec calls t.Skip with an explanation. 72 | func MustHaveExec(t testing.TB) { 73 | switch runtime.GOOS { 74 | case "wasip1", "js", "ios": 75 | t.Skipf("skipping test: may not be able to exec subprocess on %s/%s", runtime.GOOS, runtime.GOARCH) 76 | } 77 | } 78 | 79 | // Go1Point returns the x in Go 1.x. 80 | func Go1Point() int { 81 | for i := len(build.Default.ReleaseTags) - 1; i >= 0; i-- { 82 | var version int 83 | if _, err := fmt.Sscanf(build.Default.ReleaseTags[i], "go1.%d", &version); err != nil { 84 | continue 85 | } 86 | return version 87 | } 88 | panic("bad release tags") 89 | } 90 | 91 | // NeedsGo1Point skips t if the Go version used to run the test is older than 92 | // 1.x. 93 | func NeedsGo1Point(t testing.TB, x int) { 94 | if Go1Point() < x { 95 | t.Helper() 96 | t.Skipf("running Go version %q is version 1.%d, older than required 1.%d", runtime.Version(), Go1Point(), x) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /internal/configstore/download.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package configstore abstracts interaction with the telemetry config server. 6 | // Telemetry config (golang.org/x/telemetry/config) is distributed as a go 7 | // module containing go.mod and config.json. Programs that upload collected 8 | // counters download the latest config using `go mod download`. This provides 9 | // verification of downloaded configuration and cacheability. 10 | package configstore 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | "fmt" 16 | "os" 17 | "os/exec" 18 | "path/filepath" 19 | "sync/atomic" 20 | 21 | "golang.org/x/telemetry/internal/telemetry" 22 | ) 23 | 24 | const ( 25 | ModulePath = "golang.org/x/telemetry/config" 26 | configFileName = "config.json" 27 | ) 28 | 29 | // needNoConsole is used on windows to set the windows.CREATE_NO_WINDOW 30 | // creation flag. 31 | var needNoConsole = func(cmd *exec.Cmd) {} 32 | 33 | var downloads int64 34 | 35 | // Downloads reports, for testing purposes, the number of times [Download] has 36 | // been called. 37 | func Downloads() int64 { 38 | return atomic.LoadInt64(&downloads) 39 | } 40 | 41 | // Download fetches the requested telemetry UploadConfig using "go mod 42 | // download". If envOverlay is provided, it is appended to the environment used 43 | // for invoking the go command. 44 | // 45 | // The second result is the canonical version of the requested configuration. 46 | func Download(version string, envOverlay []string) (*telemetry.UploadConfig, string, error) { 47 | atomic.AddInt64(&downloads, 1) 48 | 49 | if version == "" { 50 | version = "latest" 51 | } 52 | modVer := ModulePath + "@" + version 53 | var stdout, stderr bytes.Buffer 54 | cmd := exec.Command("go", "mod", "download", "-json", modVer) 55 | needNoConsole(cmd) 56 | cmd.Env = append(os.Environ(), envOverlay...) 57 | cmd.Stdout = &stdout 58 | cmd.Stderr = &stderr 59 | if err := cmd.Run(); err != nil { 60 | var info struct { 61 | Error string 62 | } 63 | if err := json.Unmarshal(stdout.Bytes(), &info); err == nil && info.Error != "" { 64 | return nil, "", fmt.Errorf("failed to download config module: %v", info.Error) 65 | } 66 | return nil, "", fmt.Errorf("failed to download config module: %w\n%s", err, &stderr) 67 | } 68 | 69 | var info struct { 70 | Dir string 71 | Version string 72 | Error string 73 | } 74 | if err := json.Unmarshal(stdout.Bytes(), &info); err != nil || info.Dir == "" { 75 | return nil, "", fmt.Errorf("failed to download config module (invalid JSON): %w", err) 76 | } 77 | data, err := os.ReadFile(filepath.Join(info.Dir, configFileName)) 78 | if err != nil { 79 | return nil, "", fmt.Errorf("invalid config module: %w", err) 80 | } 81 | cfg := new(telemetry.UploadConfig) 82 | if err := json.Unmarshal(data, cfg); err != nil { 83 | return nil, "", fmt.Errorf("invalid config: %w", err) 84 | } 85 | return cfg, info.Version, nil 86 | } 87 | -------------------------------------------------------------------------------- /internal/content/telemetrygodev/static/charts.min.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../../shared/treenav.css", "../../shared/chartbrowser.css", "../charts.css"], 4 | "sourcesContent": ["/*!\n * Copyright 2024 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n.js-Tree ul {\n list-style: none;\n padding-left: 0;\n}\n\n.js-Tree-item ul {\n display: none;\n}\n\n.js-Tree-item {\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 0.125rem 0 0.125rem 0;\n}\n\n.js-Tree-item[aria-expanded='true'] ul {\n display: block;\n}\n\n.js-Tree-item .js-Tree-item {\n position: relative;\n padding-left: 1.25rem;\n}\n\n.js-Tree-item .js-Tree-item[aria-selected='true']:before {\n background-color: var(--color-brand-primary);\n border-radius: 50%;\n content: \"\";\n display: block;\n height: .3125rem;\n left: .4688rem;\n position: absolute;\n top: .75rem;\n width: .3125rem;\n}\n\n.js-Tree-item>a {\n color: var(--color-text-subtle);\n font-size: .875rem;\n}\n\n.js-Tree-item[aria-selected='true']>a {\n color: var(--color-text);\n}\n\n", "/*!\n * Copyright 2024 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n@import url(\"../shared/treenav.css\");\n\n/* Fix tooltip background for dark theme */\nsvg g[aria-label=\"tip\"] g {\n fill: var(--color-background);\n}\n\n.Chartbrowser-view {\n display: flex;\n flex-direction: row;\n}\n.Chartbrowser-index {\n flex: 1 1;\n padding: 0 1.5rem 0 0;\n}\n.Chartbrowser-heading {\n font-weight: bold;\n font-size: 1.25rem;\n margin: 0 0 0.5rem 0;\n}\n.Chartbrowser-index-sticky {\n position: sticky;\n top: 1rem;\n width: 10rem;\n}\n.Chartbrowser-index-sticky > ul {\n position: sticky;\n top: 1rem;\n margin-top: 0;\n}\n.Chartbrowser-link {\n color: var(--color-text-subtle);\n font-size: .875rem;\n line-height: 1.5rem;\n}\n.Chartbrowser-program {\n font-weight: normal;\n margin: 0 0 1rem 0;\n}\n.Chartbrowser-program:not(:first-of-type) {\n margin-top: 2rem;\n}\n.Chartbrowser-chart {\n background-color: var(--color-background);\n border: 1px solid transparent;\n margin-bottom: 1rem;\n padding: 0.875rem;\n box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .3), 0 1px 3px 1px rgba(60, 64, 67, .15);\n}\n.Chartbrowser-chart-name {\n text-align: center;\n margin: 0;\n}\n", "/*!\n * Copyright 2023 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n\n@import url(\"../shared/chartbrowser.css\");\n\n.Charts {\n margin-top: 1.5rem;\n}\n"], 5 | "mappings": ";AAMA,YACE,gBACA,eAGF,iBACE,aAGF,cACE,gBACA,uBAjBF,kBAqBA,qCACE,cAGF,4BACE,kBACA,qBAGF,uDACE,4CA/BF,kBAiCE,WACA,cACA,gBACA,cACA,kBACA,WACA,eAGF,gBACE,+BACA,kBAGF,oCACE,wBCvCF,wBACE,6BAGF,mBACE,aACA,mBAEF,oBACE,SAlBF,qBAqBA,sBACE,gBACA,kBAvBF,iBA0BA,2BACE,gBACA,SACA,YAEF,8BACE,gBACA,SACA,aAEF,mBACE,+BACA,kBACA,mBAEF,sBACE,gBA1CF,gBA6CA,0CACE,gBAEF,oBACE,yCACA,6BACA,mBAnDF,gBAqDE,uDAEF,yBACE,kBAxDF,SCSA,QACE", 6 | "names": [] 7 | } 8 | -------------------------------------------------------------------------------- /internal/content/shared/_color.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2021 The Go Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | :root { 8 | /* Colors */ 9 | --gray-1: #202224; 10 | --gray-2: #3e4042; 11 | --gray-3: #555759; 12 | --gray-4: #6e7072; 13 | --gray-5: #848688; 14 | --gray-6: #aaacae; 15 | --gray-7: #c6c8ca; 16 | --gray-8: #dcdee0; 17 | --gray-9: #f0f1f2; 18 | --gray-10: #f8f8f8; 19 | --turq-light: #5dc9e2; 20 | --turq-med: #50b7e0; 21 | --turq-dark: #007d9c; 22 | --blue: #bfeaf4; 23 | --blue-light: #f2fafd; 24 | --black: #000; 25 | --green: #3a6e11; 26 | --green-light: #5fda64; 27 | --pink: #c85e7a; 28 | --pink-light: #fdecf1; 29 | --purple: #542c7d; 30 | --slate: #253443; /* Footer background. */ 31 | --white: #fff; 32 | --yellow: #fceea5; 33 | --yellow-light: #fff8cc; 34 | 35 | /* Color Intents */ 36 | --color-brand-primary: var(--turq-dark); 37 | --color-background: var(--white); 38 | --color-background-inverted: var(--slate); 39 | --color-background-accented: var(--gray-10); 40 | --color-background-highlighted: var(--blue); 41 | --color-background-highlighted-link: var(--blue-light); 42 | --color-background-info: var(--gray-9); 43 | --color-background-warning: var(--yellow-light); 44 | --color-background-alert: var(--pink-light); 45 | --color-border: var(--gray-7); 46 | --color-text: var(--gray-1); 47 | --color-text-subtle: var(--gray-4); 48 | --color-text-link: var(--turq-dark); 49 | --color-text-inverted: var(--white); 50 | --color-code-comment: var(--green); 51 | 52 | /* Interactive Colors */ 53 | --color-input: var(--color-background); 54 | --color-input-text: var(--color-text); 55 | --color-button: var(--turq-dark); 56 | --color-button-disabled: var(--gray-9); 57 | --color-button-text: var(--white); 58 | --color-button-text-disabled: var(--gray-3); 59 | --color-button-inverted: var(--color-background); 60 | --color-button-inverted-disabled: var(--color-background); 61 | --color-button-inverted-text: var(--color-brand-primary); 62 | --color-button-inverted-text-disabled: var(--color-text-subtle); 63 | --color-button-accented: var(--yellow); 64 | --color-button-accented-disabled: var(--gray-9); 65 | --color-button-accented-text: var(--gray-1); 66 | --color-button-accented-text-disabled: var(--gray-3); 67 | } 68 | 69 | @media (prefers-color-scheme: dark) { 70 | :root:not([data-theme="light"]) { 71 | --color-brand-primary: var(--turq-med); 72 | --color-background: var(--gray-1); 73 | --color-background-accented: var(--gray-2); 74 | --color-background-highlighted: var(--gray-2); 75 | --color-background-highlighted-link: var(--gray-2); 76 | --color-background-info: var(--gray-3); 77 | --color-background-warning: var(--yellow); 78 | --color-background-alert: var(--pink); 79 | --color-border: var(--gray-4); 80 | --color-text: var(--gray-9); 81 | --color-text-link: var(--turq-med); 82 | --color-text-subtle: var(--gray-7); 83 | --color-code-comment: var(--green-light); 84 | } 85 | 86 | img.go-Icon { 87 | filter: invert(1); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /internal/telemetry/proginfo_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package telemetry_test 6 | 7 | import ( 8 | "fmt" 9 | "path" 10 | "runtime/debug" 11 | "testing" 12 | 13 | "golang.org/x/telemetry/internal/telemetry" 14 | ) 15 | 16 | func TestProgramInfo_ProgramVersion(t *testing.T) { 17 | tests := []struct { 18 | path string 19 | version string 20 | want string 21 | }{ 22 | { 23 | path: "golang.org/x/tools/gopls", 24 | version: "(devel)", 25 | want: "devel", 26 | }, 27 | { 28 | path: "golang.org/x/tools/gopls", 29 | version: "", 30 | want: "", 31 | }, 32 | { 33 | path: "golang.org/x/tools/gopls", 34 | version: "v0.14.0-pre.1", 35 | want: "v0.14.0-pre.1", 36 | }, 37 | { 38 | path: "golang.org/x/tools/gopls", 39 | version: "v0.0.0-20231207172801-3c8b0df0c3fd", 40 | want: "devel", 41 | }, 42 | { 43 | path: "cmd/go", 44 | version: "", 45 | want: "go1.23.0", // hard-coded below 46 | }, 47 | { 48 | path: "cmd/compile", 49 | version: "", 50 | want: "go1.23.0", // hard-coded below 51 | }, 52 | } 53 | buildInfo, ok := debug.ReadBuildInfo() 54 | if !ok { 55 | t.Fatal("cannot use debug.ReadBuildInfo") 56 | } 57 | 58 | for _, tt := range tests { 59 | name := fmt.Sprintf("%s@%s", path.Base(tt.path), tt.version) 60 | t.Run(name, func(t *testing.T) { 61 | in := *buildInfo 62 | in.GoVersion = "go1.23.0" 63 | in.Path = tt.path 64 | in.Main.Version = tt.version 65 | _, _, got := telemetry.ProgramInfo(&in) 66 | if got != tt.want { 67 | t.Errorf("program version = %q, want %q", got, tt.want) 68 | } 69 | }) 70 | } 71 | } 72 | 73 | func TestProgramInfo_GoVersion(t *testing.T) { 74 | tests := []struct { 75 | goVersion string 76 | wantGoVers string 77 | }{ 78 | { 79 | "go1.23.0-bigcorp", 80 | "devel", 81 | }, 82 | { 83 | "go1.23.0", 84 | "go1.23.0", 85 | }, 86 | { 87 | "go1.26rc1", 88 | "go1.26rc1", 89 | }, 90 | { 91 | "go1.25-devel_9ce47e66e8 Wed Mar 26 03:48:50 2025 -0700", 92 | "devel", 93 | }, 94 | { 95 | "devel go1.24-0d6bb68f48 Thu Jul 25 23:27:41 2024 -0600", 96 | "devel", 97 | }, 98 | { 99 | "go1.26rc1-X:nodwarf5", 100 | "devel", 101 | }, 102 | { 103 | "go1.23rc2 X:aliastypeparams", 104 | "devel", 105 | }, 106 | } 107 | buildInfo, ok := debug.ReadBuildInfo() 108 | if !ok { 109 | t.Fatal("cannot use debug.ReadBuildInfo") 110 | } 111 | 112 | for _, tt := range tests { 113 | t.Run(tt.goVersion, func(t *testing.T) { 114 | in := *buildInfo 115 | in.GoVersion = tt.goVersion 116 | in.Path = "cmd/go" 117 | in.Main.Version = tt.goVersion 118 | gotGoVers, _, gotProgVers := telemetry.ProgramInfo((&in)) 119 | if gotGoVers != tt.wantGoVers { 120 | t.Errorf("go version = %q, want %q", gotGoVers, tt.wantGoVers) 121 | } 122 | if gotProgVers != tt.wantGoVers { 123 | t.Errorf("program version = %q, want %q", gotProgVers, tt.wantGoVers) 124 | } 125 | }) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /internal/telemetry/dir_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package telemetrymode manages the telemetry mode file. 6 | package telemetry 7 | 8 | import ( 9 | "os" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func TestDefaults(t *testing.T) { 15 | defaultDirMissing := false 16 | if _, err := os.UserConfigDir(); err != nil { 17 | defaultDirMissing = true 18 | } 19 | if defaultDirMissing { 20 | if Default.LocalDir() != "" || Default.UploadDir() != "" || Default.ModeFile() != "" { 21 | t.Errorf("DefaultSetting: (%q, %q, %q), want empty LocalDir/UploadDir/ModeFile", Default.LocalDir(), Default.UploadDir(), Default.ModeFile()) 22 | } 23 | } else { 24 | if Default.LocalDir() == "" || Default.UploadDir() == "" || Default.ModeFile() == "" { 25 | t.Errorf("DefaultSetting: (%q, %q, %q), want non-empty LocalDir/UploadDir/ModeFile", Default.LocalDir(), Default.UploadDir(), Default.ModeFile()) 26 | } 27 | } 28 | } 29 | 30 | func TestTelemetryModeWithNoModeConfig(t *testing.T) { 31 | tests := []struct { 32 | dir Dir 33 | want string 34 | }{ 35 | {NewDir(t.TempDir()), "local"}, 36 | {Dir{}, "off"}, 37 | } 38 | for _, tt := range tests { 39 | if got, _ := tt.dir.Mode(); got != tt.want { 40 | t.Errorf("Dir{modefile=%q}.Mode() = %v, want %v", tt.dir.ModeFile(), got, tt.want) 41 | } 42 | } 43 | } 44 | 45 | func TestSetMode(t *testing.T) { 46 | tests := []struct { 47 | in string 48 | wantErr bool // want error when setting. 49 | }{ 50 | {"on", false}, 51 | {"off", false}, 52 | {"local", false}, 53 | {"https://mytelemetry.com", true}, 54 | {"http://insecure.com", true}, 55 | {"bogus", true}, 56 | {"", true}, 57 | } 58 | for _, tt := range tests { 59 | t.Run("mode="+tt.in, func(t *testing.T) { 60 | dir := NewDir(t.TempDir()) 61 | setErr := dir.SetMode(tt.in) 62 | if (setErr != nil) != tt.wantErr { 63 | t.Fatalf("Set() error = %v, wantErr %v", setErr, tt.wantErr) 64 | } 65 | if setErr != nil { 66 | return 67 | } 68 | if got, _ := dir.Mode(); got != tt.in { 69 | t.Errorf("LookupMode() = %q, want %q", got, tt.in) 70 | } 71 | }) 72 | } 73 | } 74 | 75 | func TestMode(t *testing.T) { 76 | tests := []struct { 77 | in string 78 | wantMode string 79 | wantTime time.Time 80 | }{ 81 | {"on", "on", time.Time{}}, 82 | {"on 2023-09-26", "on", time.Date(2023, time.September, 26, 0, 0, 0, 0, time.UTC)}, 83 | {"off", "off", time.Time{}}, 84 | {"local", "local", time.Time{}}, 85 | } 86 | for _, tt := range tests { 87 | t.Run("mode="+tt.in, func(t *testing.T) { 88 | dir := NewDir(t.TempDir()) 89 | if err := os.WriteFile(dir.ModeFile(), []byte(tt.in), 0666); err != nil { 90 | t.Fatal(err) 91 | } 92 | // Note: the checks below intentionally do not use time.Equal: 93 | // we want this exact representation of time. 94 | if gotMode, gotTime := dir.Mode(); gotMode != tt.wantMode || gotTime != tt.wantTime { 95 | t.Errorf("ModeFilePath(contents=%s).Mode() = %q, %v, want %q, %v", tt.in, gotMode, gotTime, tt.wantMode, tt.wantTime) 96 | } 97 | }) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /internal/configgen/validate_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/telemetry/internal/chartconfig" 12 | ) 13 | 14 | func TestLoadedChartsAreValid(t *testing.T) { 15 | // Test that we can actually load the chart config. 16 | charts, err := chartconfig.Load() 17 | if err != nil { 18 | t.Fatal("chartconfig.Load() failed:", err) 19 | } 20 | for i, chart := range charts { 21 | if err := ValidateChartConfig(chart); err != nil { 22 | t.Errorf("Chart %d is invalid: %v", i, err) 23 | } 24 | } 25 | 26 | if t.Failed() { 27 | // Skip the the rest of the test, it's redundant if 28 | // the chartconfig value isn't valid. 29 | return 30 | } 31 | 32 | // Test that all paddings are complete for the purposes 33 | // of being able to generate from the chartconfig value. 34 | for _, tc := range [...]struct { 35 | name string 36 | paddings map[string]padding 37 | }{ 38 | {"regularPaddings", regularPaddings}, 39 | {"minimumPaddings", minimumPaddings}, 40 | } { 41 | t.Run(tc.name, func(t *testing.T) { 42 | if testing.Short() { 43 | t.Skip("not running test that uses internet in short mode") 44 | } 45 | _, err := generate(charts, tc.paddings) 46 | if err != nil { 47 | t.Errorf("generate(charts, %s): %v", tc.name, err) 48 | } 49 | }) 50 | } 51 | } 52 | 53 | func TestValidateOK(t *testing.T) { 54 | // A minimally valid chart config. 55 | const input = ` 56 | title: Editor Distribution 57 | counter: gopls/editor:{emacs,vim,vscode,other} 58 | type: partition 59 | issue: https://go.dev/issue/12345 60 | program: golang.org/x/tools/gopls 61 | module: golang.org/x/tools/gopls 62 | ` 63 | records, err := chartconfig.Parse([]byte(input)) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | if len(records) != 1 { 68 | t.Fatalf("Parse(%q) returned %d records, want exactly 1", input, len(records)) 69 | } 70 | if err := ValidateChartConfig(records[0]); err != nil { 71 | t.Errorf("Validate(%q) = %v, want nil", input, err) 72 | } 73 | } 74 | 75 | func TestValidate(t *testing.T) { 76 | tests := map[string][]string{ // input -> want errors 77 | // validation of mandatory fields 78 | "description:bar": {"title", "program", "issue", "counter", "type"}, 79 | 80 | // validation of semver intervals 81 | "version:1.2.3.4": {"semver"}, 82 | 83 | // valid of stack configuration 84 | "depth:-1": {"non-negative", "stack"}, 85 | } 86 | 87 | for input, wantErrs := range tests { 88 | records, err := chartconfig.Parse([]byte(input)) 89 | if err != nil { 90 | t.Fatal(err) 91 | } 92 | if len(records) != 1 { 93 | t.Fatalf("Parse(%q) returned %d records, want exactly 1", input, len(records)) 94 | } 95 | err = ValidateChartConfig(records[0]) 96 | if err == nil { 97 | t.Fatalf("Validate(%q) succeeded unexpectedly", input) 98 | } 99 | errs := err.Error() 100 | for _, want := range wantErrs { 101 | if !strings.Contains(errs, want) { 102 | t.Errorf("Validate(%q) = %v, want containing %q", input, err, want) 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /internal/content/shared/treenav.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2024 The Go Authors. All rights reserved. 4 | * Use of this source code is governed by a BSD-style 5 | * license that can be found in the LICENSE file. 6 | */ 7 | 8 | /** 9 | * A treeNavController adds dynamic expansion and selection of index list 10 | * elements based on scroll position. 11 | * 12 | * Use it as follows: 13 | * - Add the .js-Tree class to a parent element of your index and content. 14 | * - Add the .js-Tree-item class to
  • elements of your index. 15 | * - Add the .js-Tree-heading class to heading elements of your content. 16 | * 17 | * Then, when you scroll content, the 'aria-selected' and 'aria-expanded' 18 | * attributes of your tree items will be set according to the current content 19 | * scroll position. The included treenav.css implements styling to expand and 20 | * highlight index elements according to these attributes. 21 | */ 22 | export function treeNavController(el: HTMLElement) { 23 | const headings = el.querySelectorAll(".js-Tree-heading"); 24 | const callback = () => { 25 | // Collect heading elements above the scroll position. 26 | let above: HTMLHeadingElement[] = []; 27 | for (const h of headings) { 28 | const rect = h.getBoundingClientRect(); 29 | if (rect.height && rect.top < 80) { 30 | above.unshift(h); 31 | } 32 | } 33 | // Highlight the first heading even if we're not yet scrolled below it. 34 | if (above.length == 0 && headings[0] instanceof HTMLHeadingElement) { 35 | above = [headings[0]]; 36 | } 37 | // Collect the set of heading levels we're immediately below, at most one 38 | // per heading level, by decresing level. 39 | // e.g. [

    ,

    ,

    ] 40 | let threshold = Infinity; 41 | const active: HTMLHeadingElement[] = []; 42 | for (const h of above) { 43 | const level = Number(h.tagName[1]); 44 | if (level < threshold) { 45 | threshold = level; 46 | active.push(h); 47 | } 48 | } 49 | // Update aria-selected and aria-expanded for all items, per the current 50 | // position. 51 | const navItems = el.querySelectorAll(".js-Tree-item"); 52 | for (const item of navItems) { 53 | const headingId = item.dataset["headingId"]; 54 | let selected = false, 55 | expanded = false; 56 | for (const h of active) { 57 | if (h.id === headingId) { 58 | if (h === active[0]) { 59 | selected = true; 60 | } else { 61 | expanded = true; 62 | } 63 | break; 64 | } 65 | } 66 | item.setAttribute("aria-selected", selected ? "true" : "false"); 67 | item.setAttribute("aria-expanded", expanded ? "true" : "false"); 68 | } 69 | }; 70 | 71 | // Update on changes to viewport intersection, defensively debouncing to 72 | // guard against performance issues. 73 | const observer = new IntersectionObserver(debounce(callback, 20)); 74 | for (const h of headings) { 75 | observer.observe(h); 76 | } 77 | } 78 | 79 | export function debounce unknown>( 80 | callback: T, 81 | wait: number 82 | ) { 83 | let timeout: number; 84 | return (...args: unknown[]) => { 85 | clearTimeout(timeout); 86 | timeout = setTimeout(() => callback(...args), wait); 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /internal/proxy/proxy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package proxy 6 | 7 | import ( 8 | "archive/zip" 9 | "fmt" 10 | "io" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | "testing" 15 | ) 16 | 17 | func TestWriteModuleVersion(t *testing.T) { 18 | tests := []struct { 19 | modulePath, version string 20 | files map[string][]byte 21 | }{ 22 | { 23 | modulePath: "mod.test/module", 24 | version: "v1.2.3", 25 | files: map[string][]byte{ 26 | "go.mod": []byte("module mod.com\n\ngo 1.12"), 27 | "const.go": []byte("package module\n\nconst Answer = 42"), 28 | }, 29 | }, 30 | { 31 | modulePath: "mod.test/module", 32 | version: "v1.2.4", 33 | files: map[string][]byte{ 34 | "go.mod": []byte("module mod.com\n\ngo 1.12"), 35 | "const.go": []byte("package module\n\nconst Answer = 43"), 36 | }, 37 | }, 38 | { 39 | modulePath: "mod.test/nogomod", 40 | version: "v0.9.0", 41 | files: map[string][]byte{ 42 | "const.go": []byte("package module\n\nconst Other = \"Other\""), 43 | }, 44 | }, 45 | } 46 | dir := t.TempDir() 47 | defer os.RemoveAll(dir) 48 | for _, test := range tests { 49 | // Since we later assert on the contents of /list, don't use subtests. 50 | if err := writeModuleVersion(dir, test.modulePath, test.version, test.files); err != nil { 51 | t.Fatal(err) 52 | } 53 | rootDir := filepath.Join(dir, filepath.FromSlash(test.modulePath), "@v") 54 | gomod, err := os.ReadFile(filepath.Join(rootDir, test.version+".mod")) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | wantMod, ok := test.files["go.mod"] 59 | if !ok { 60 | wantMod = []byte("module " + test.modulePath) 61 | } 62 | if got, want := string(gomod), string(wantMod); got != want { 63 | t.Errorf("reading %s/@v/%s.mod: got %q, want %q", test.modulePath, test.version, got, want) 64 | } 65 | zr, err := zip.OpenReader(filepath.Join(rootDir, test.version+".zip")) 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | defer zr.Close() 70 | 71 | for _, zf := range zr.File { 72 | r, err := zf.Open() 73 | if err != nil { 74 | t.Fatal(err) 75 | } 76 | defer r.Close() 77 | content, err := io.ReadAll(r) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | name := strings.TrimPrefix(zf.Name, fmt.Sprintf("%s@%s/", test.modulePath, test.version)) 82 | if got, want := string(content), string(test.files[name]); got != want { 83 | t.Errorf("unzipping %q: got %q, want %q", zf.Name, got, want) 84 | } 85 | delete(test.files, name) 86 | } 87 | for name := range test.files { 88 | t.Errorf("file %q not present in the module zip", name) 89 | } 90 | } 91 | 92 | lists := []struct { 93 | modulePath, want string 94 | }{ 95 | {"mod.test/module", "v1.2.3\nv1.2.4\n"}, 96 | {"mod.test/nogomod", "v0.9.0\n"}, 97 | } 98 | 99 | for _, test := range lists { 100 | fp := filepath.Join(dir, filepath.FromSlash(test.modulePath), "@v", "list") 101 | list, err := os.ReadFile(fp) 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | if got := string(list); got != test.want { 106 | t.Errorf("%q/@v/list: got %q, want %q", test.modulePath, got, test.want) 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /godev/devtools/cmd/esbuild/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !plan9 6 | 7 | // Command esbuild bundles and minifies stylesheet and typescript files. 8 | // 9 | // The command will walk the directory it is run in to gather the set of 10 | // entrypoints and filenames beginning with an underscore are ignored. 11 | // 12 | // go run golang.org/x/telemetry/godev/devtools/cmd/esbuild 13 | // 14 | // You can also pass a directory as an argument to the command. 15 | // 16 | // go run golang.org/x/telemetry/godev/devtools/cmd/esbuild directory 17 | // 18 | // By default the command writes the output files to the same directory as 19 | // the entrypoints with .min.css or .min.js extensions for .css and .ts files 20 | // respectively. Override the output directory with a flag. 21 | // 22 | // go run golang.org/x/telemetry/godev/devtools/cmd/esbuild --outdir static 23 | // 24 | // To watch the entrypoints and rebuild the output files on changes use the 25 | // watch flag. 26 | // 27 | // go run golang.org/x/telemetry/godev/devtools/cmd/esbuild --watch 28 | package main 29 | 30 | import ( 31 | "flag" 32 | "io/fs" 33 | "log" 34 | "os" 35 | "path" 36 | "strings" 37 | 38 | "github.com/evanw/esbuild/pkg/api" 39 | ) 40 | 41 | var ( 42 | outdir = flag.String("outdir", ".", "output directory for the build operation") 43 | watch = flag.Bool("watch", false, "listen for changes on the filesystem and automatically rebuild") 44 | ) 45 | 46 | func main() { 47 | flag.Parse() 48 | dirs := []string{"."} 49 | if len(flag.Args()) > 0 { 50 | dirs = flag.Args() 51 | } 52 | for _, dir := range dirs { 53 | opts := api.BuildOptions{ 54 | Banner: map[string]string{"css": "/* Code generated by esbuild. DO NOT EDIT. */", "js": "// Code generated by esbuild. DO NOT EDIT."}, 55 | Bundle: true, 56 | EntryPoints: entrypoints(dir), 57 | LogLevel: api.LogLevelInfo, 58 | MinifyWhitespace: true, 59 | MinifyIdentifiers: true, 60 | MinifySyntax: true, 61 | OutExtension: map[string]string{".css": ".min.css", ".js": ".min.js"}, 62 | External: []string{"*.svg"}, 63 | Outdir: *outdir, 64 | Sourcemap: api.SourceMapLinked, 65 | Write: true, 66 | } 67 | if *watch { 68 | ctx, err := api.Context(opts) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | if err := ctx.Watch(api.WatchOptions{}); err != nil { 73 | log.Fatal(err) 74 | } 75 | // Returning from main() exits immediately in Go. 76 | // Block forever so we keep watching and don't exit. 77 | <-make(chan struct{}) 78 | } else { 79 | result := api.Build(opts) 80 | if len(result.Errors) > 0 { 81 | // esbuild already logs errors 82 | os.Exit(1) 83 | } 84 | } 85 | } 86 | } 87 | 88 | func entrypoints(dir string) []string { 89 | var e []string 90 | if err := fs.WalkDir(os.DirFS(dir), ".", func(p string, d fs.DirEntry, err error) error { 91 | if err != nil { 92 | return err 93 | } 94 | base := path.Base(p) 95 | if strings.HasPrefix(base, "_") || strings.HasSuffix(base, ".min.css") || strings.HasSuffix(base, ".min.js") { 96 | return nil 97 | } 98 | switch path.Ext(p) { 99 | case ".css", ".ts": 100 | e = append(e, path.Join(dir, p)) 101 | } 102 | return nil 103 | }); err != nil { 104 | log.Fatal(err) 105 | } 106 | return e 107 | } 108 | -------------------------------------------------------------------------------- /internal/counter/concurrent_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package counter_test 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | "sync" 13 | "testing" 14 | "time" 15 | 16 | "golang.org/x/telemetry/counter/countertest" 17 | "golang.org/x/telemetry/internal/counter" 18 | "golang.org/x/telemetry/internal/regtest" 19 | "golang.org/x/telemetry/internal/telemetry" 20 | "golang.org/x/telemetry/internal/testenv" 21 | ) 22 | 23 | func init() { 24 | // Catch any bugs encountered while mapping counters. 25 | counter.CrashOnBugs = true 26 | } 27 | 28 | func TestConcurrentExtension(t *testing.T) { 29 | testenv.SkipIfUnsupportedPlatform(t) 30 | 31 | // This test verifies that files may be concurrently extended: when one file 32 | // discovers that its entries exceed the mapped data, it remaps the data. 33 | 34 | // Both programs populate enough new records to extend the file multiple 35 | // times. 36 | const numCounters = 50000 37 | prog1 := regtest.NewProgram(t, "inc1", func() int { 38 | for i := 0; i < numCounters; i++ { 39 | counter.New(fmt.Sprint("gophers", i)).Inc() 40 | } 41 | return 0 42 | }) 43 | prog2 := regtest.NewProgram(t, "inc2", func() int { 44 | for i := numCounters; i < 2*numCounters; i++ { 45 | counter.New(fmt.Sprint("gophers", i)).Inc() 46 | } 47 | return 0 48 | }) 49 | 50 | dir := t.TempDir() 51 | now := time.Now().UTC() 52 | 53 | // Run a no-op program in the telemetry dir to ensure that the weekends file 54 | // exists, and avoid the race described in golang/go#68390. 55 | // (We could also call countertest.Open here, but better to avoid mutating 56 | // state in the current process for a test that is otherwise hermetic) 57 | prog0 := regtest.NewProgram(t, "init", func() int { return 0 }) 58 | if _, err := regtest.RunProgAsOf(t, dir, now, prog0); err != nil { 59 | t.Fatal(err) 60 | } 61 | 62 | var wg sync.WaitGroup 63 | wg.Add(2) 64 | 65 | // Run the programs concurrently. 66 | go func() { 67 | defer wg.Done() 68 | if out, err := regtest.RunProgAsOf(t, dir, now, prog1); err != nil { 69 | t.Errorf("prog1 failed: %v; output:\n%s", err, out) 70 | } 71 | }() 72 | go func() { 73 | defer wg.Done() 74 | if out, err := regtest.RunProgAsOf(t, dir, now, prog2); err != nil { 75 | t.Errorf("prog2 failed: %v; output:\n%s", err, out) 76 | } 77 | }() 78 | 79 | wg.Wait() 80 | 81 | counts := readCountsForDir(t, telemetry.NewDir(dir).LocalDir()) 82 | if got, want := len(counts), 2*numCounters; got != want { 83 | t.Errorf("Got %d counters, want %d", got, want) 84 | } 85 | 86 | for name, value := range counts { 87 | if value != 1 { 88 | t.Errorf("count(%s) = %d, want 1", name, value) 89 | } 90 | } 91 | } 92 | 93 | func readCountsForDir(t *testing.T, dir string) map[string]uint64 { 94 | entries, err := os.ReadDir(dir) 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | var countFiles []string 99 | for _, entry := range entries { 100 | if strings.HasSuffix(entry.Name(), ".count") { 101 | countFiles = append(countFiles, filepath.Join(dir, entry.Name())) 102 | } 103 | } 104 | if len(countFiles) != 1 { 105 | t.Fatalf("found %d count files, want 1; directory contents: %v", len(countFiles), entries) 106 | } 107 | 108 | counters, _, err := countertest.ReadFile(countFiles[0]) 109 | if err != nil { 110 | t.Fatal(err) 111 | } 112 | return counters 113 | } 114 | -------------------------------------------------------------------------------- /godev/devtools/cmd/copyuploads/copyuploads.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // The copyuploads command copies uploads from GCS to the local filesystem 6 | // storage, for use with local development of the worker. 7 | // 8 | // By default, this command copies the last 3 days of uploads from 9 | // telemetry.go.dev to the local filesystem bucket local-telemetry-uploaded, at 10 | // which point this data will be available when running ./godev/cmd/worker with 11 | // no arguments. 12 | // 13 | // See --help for more details. 14 | package main 15 | 16 | import ( 17 | "bytes" 18 | "context" 19 | "encoding/json" 20 | "flag" 21 | "fmt" 22 | "io" 23 | "log" 24 | "net/http" 25 | "os" 26 | "path" 27 | "time" 28 | 29 | "golang.org/x/telemetry/godev/internal/config" 30 | "golang.org/x/telemetry/godev/internal/storage" 31 | ) 32 | 33 | var ( 34 | daysBack = flag.Int("days_back", 3, "The number of days back to copy") 35 | verbose = flag.Bool("v", false, "If set, enable verbose logging.") 36 | ) 37 | 38 | func main() { 39 | flag.Parse() 40 | 41 | cfg := config.NewConfig() 42 | ctx := context.Background() 43 | 44 | fs, err := storage.NewFSBucket(ctx, cfg.LocalStorage, "local-telemetry-uploaded") 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | 49 | start := time.Now() 50 | for dayOffset := range *daysBack { 51 | date := start.AddDate(0, 0, -dayOffset-1) // today's merged reports may not yet be available 52 | dateString := date.Format(time.DateOnly) 53 | byFile, err := downloadData(dateString) 54 | if err != nil { 55 | log.Fatalf("Downloading data for %s: %v", dateString, err) 56 | } 57 | for name, content := range byFile { 58 | // Skip objects that already exist in local storage. 59 | dest := fs.Object(path.Join(dateString, name)) 60 | if _, err := os.Stat(dest.(*storage.FSObject).Filename()); err == nil { 61 | if *verbose { 62 | log.Printf("Skipping existing object %s", name) 63 | } 64 | continue 65 | } 66 | if *verbose { 67 | log.Printf("Starting copying object %s", name) 68 | } 69 | w, err := dest.NewWriter(ctx) 70 | if err != nil { 71 | log.Fatal(err) 72 | } 73 | if _, err := io.Copy(w, bytes.NewReader(content)); err != nil { 74 | log.Fatal(err) 75 | } 76 | } 77 | } 78 | } 79 | 80 | // downloadData downloads the merged telemetry data for the given date string 81 | // (which must be in time.DateOnly format), and splits it back into individual 82 | // uploaded files, keyed by their original name (.json). 83 | func downloadData(dateString string) (map[string][]byte, error) { 84 | url := fmt.Sprintf("https://storage.googleapis.com/prod-telemetry-merged/%s.json", dateString) 85 | resp, err := http.Get(url) 86 | if err != nil { 87 | return nil, err 88 | } 89 | defer resp.Body.Close() 90 | 91 | if resp.StatusCode != 200 { 92 | return nil, fmt.Errorf("downloading %s failed with status %d", url, resp.StatusCode) 93 | } 94 | 95 | data, err := io.ReadAll(resp.Body) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | byFile := make(map[string][]byte) 101 | for _, line := range bytes.Split(data, []byte("\n")) { 102 | line = bytes.TrimSpace(line) 103 | if len(line) == 0 { 104 | continue // defensive: skip empty lines 105 | } 106 | var x struct{ X float64 } 107 | if err := json.Unmarshal(line, &x); err != nil { 108 | return nil, err 109 | } 110 | file := fmt.Sprintf("%g.json", x.X) 111 | byFile[file] = append(line, '\n') // uploaded data is newline terminated 112 | } 113 | return byFile, nil 114 | } 115 | -------------------------------------------------------------------------------- /internal/content/telemetrygodev/static/index.min.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../../shared/treenav.css", "../../shared/chartbrowser.css", "../index.css"], 4 | "sourcesContent": ["/*!\n * Copyright 2024 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n.js-Tree ul {\n list-style: none;\n padding-left: 0;\n}\n\n.js-Tree-item ul {\n display: none;\n}\n\n.js-Tree-item {\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 0.125rem 0 0.125rem 0;\n}\n\n.js-Tree-item[aria-expanded='true'] ul {\n display: block;\n}\n\n.js-Tree-item .js-Tree-item {\n position: relative;\n padding-left: 1.25rem;\n}\n\n.js-Tree-item .js-Tree-item[aria-selected='true']:before {\n background-color: var(--color-brand-primary);\n border-radius: 50%;\n content: \"\";\n display: block;\n height: .3125rem;\n left: .4688rem;\n position: absolute;\n top: .75rem;\n width: .3125rem;\n}\n\n.js-Tree-item>a {\n color: var(--color-text-subtle);\n font-size: .875rem;\n}\n\n.js-Tree-item[aria-selected='true']>a {\n color: var(--color-text);\n}\n\n", "/*!\n * Copyright 2024 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n@import url(\"../shared/treenav.css\");\n\n/* Fix tooltip background for dark theme */\nsvg g[aria-label=\"tip\"] g {\n fill: var(--color-background);\n}\n\n.Chartbrowser-view {\n display: flex;\n flex-direction: row;\n}\n.Chartbrowser-index {\n flex: 1 1;\n padding: 0 1.5rem 0 0;\n}\n.Chartbrowser-heading {\n font-weight: bold;\n font-size: 1.25rem;\n margin: 0 0 0.5rem 0;\n}\n.Chartbrowser-index-sticky {\n position: sticky;\n top: 1rem;\n width: 10rem;\n}\n.Chartbrowser-index-sticky > ul {\n position: sticky;\n top: 1rem;\n margin-top: 0;\n}\n.Chartbrowser-link {\n color: var(--color-text-subtle);\n font-size: .875rem;\n line-height: 1.5rem;\n}\n.Chartbrowser-program {\n font-weight: normal;\n margin: 0 0 1rem 0;\n}\n.Chartbrowser-program:not(:first-of-type) {\n margin-top: 2rem;\n}\n.Chartbrowser-chart {\n background-color: var(--color-background);\n border: 1px solid transparent;\n margin-bottom: 1rem;\n padding: 0.875rem;\n box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .3), 0 1px 3px 1px rgba(60, 64, 67, .15);\n}\n.Chartbrowser-chart-name {\n text-align: center;\n margin: 0;\n}\n", "/*!\n * Copyright 2024 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n@import url(\"../shared/chartbrowser.css\");\n\np {\n /* Reset from _typography.css */\n max-width: none;\n}\n\n.Charts {\n margin-top: 1rem;\n}\n.Centered {\n display: flex;\n justify-content: center;\n}\n.Charts-heading {\n margin: 2.5rem 0 1.5rem;\n}\n.Charts-heading h2 {\n font-weight: normal;\n margin: 0;\n}\n.Charts-heading a {\n border: var(--border);\n padding: 0.6rem;\n border-radius: 0.5rem;\n background-color: var(--color-background-highlighted-link);\n}\n.Charts-heading ul {\n margin: 0.5rem 0 0 0;\n list-style: none;\n display: inline-flex;\n padding: 0;\n}\n.Charts-heading li {\n list-style: none;\n display: inline-flex;\n}\n.Charts-heading li:not(:last-child) {\n margin-right: 1rem;\n}\n"], 5 | "mappings": ";AAMA,YACE,gBACA,eAGF,iBACE,aAGF,cACE,gBACA,uBAjBF,kBAqBA,qCACE,cAGF,4BACE,kBACA,qBAGF,uDACE,4CA/BF,kBAiCE,WACA,cACA,gBACA,cACA,kBACA,WACA,eAGF,gBACE,+BACA,kBAGF,oCACE,wBCvCF,wBACE,6BAGF,mBACE,aACA,mBAEF,oBACE,SAlBF,qBAqBA,sBACE,gBACA,kBAvBF,iBA0BA,2BACE,gBACA,SACA,YAEF,8BACE,gBACA,SACA,aAEF,mBACE,+BACA,kBACA,mBAEF,sBACE,gBA1CF,gBA6CA,0CACE,gBAEF,oBACE,yCACA,6BACA,mBAnDF,gBAqDE,uDAEF,yBACE,kBAxDF,SCQA,EAEE,eAGF,QACE,gBAEF,UACE,aACA,uBAEF,gBApBA,uBAuBA,mBACE,gBAxBF,SA2BA,kBACI,qBA5BJ,kCA+BI,0DAEJ,mBAjCA,iBAmCE,gBACA,oBApCF,UAuCA,mBACE,gBACA,oBAEF,oCACE", 6 | "names": [] 7 | } 8 | -------------------------------------------------------------------------------- /godev/internal/middleware/middleware.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package middleware implements a simple middleware pattern for http handlers, 6 | // along with implementations for some common middlewares. 7 | package middleware 8 | 9 | import ( 10 | "fmt" 11 | "net/http" 12 | "runtime/debug" 13 | "time" 14 | 15 | "golang.org/x/exp/slog" 16 | ) 17 | 18 | // A Middleware is a func that wraps an http.Handler. 19 | type Middleware func(http.Handler) http.Handler 20 | 21 | // Chain creates a new Middleware that applies a sequence of Middlewares, so 22 | // that they execute in the given order when handling an http request. 23 | // 24 | // In other words, Chain(m1, m2)(handler) = m1(m2(handler)) 25 | // 26 | // A similar pattern is used in e.g. github.com/justinas/alice: 27 | // https://github.com/justinas/alice/blob/ce87934/chain.go#L45 28 | func Chain(middlewares ...Middleware) Middleware { 29 | return func(h http.Handler) http.Handler { 30 | for i := range middlewares { 31 | h = middlewares[len(middlewares)-1-i](h) 32 | } 33 | return h 34 | } 35 | } 36 | 37 | // Log is a middleware that logs request start, end, duration, and status. 38 | func Log(logger *slog.Logger) Middleware { 39 | return func(h http.Handler) http.Handler { 40 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 41 | start := time.Now() 42 | ctx := r.Context() 43 | l := logger.With( 44 | slog.String("method", r.Method), 45 | slog.String("uri", r.RequestURI), 46 | // TODO(hyangah): set trace context from X-Cloud-Trace-Context 47 | ) 48 | l.InfoContext(ctx, "request start") 49 | w2 := &statusRecorder{w, 200} 50 | h.ServeHTTP(w2, r) 51 | level := slog.LevelInfo 52 | msg := "request end" 53 | switch w2.status / 100 { 54 | case 5: 55 | level = slog.LevelError // 5XX error 56 | msg = "request error" 57 | case 4: 58 | level = slog.LevelWarn // 4XX error 59 | msg = "request rejected" 60 | } 61 | l.Log(ctx, level, msg, 62 | slog.Int("status", w2.status), 63 | slog.Duration("duration", time.Since(start)), 64 | ) 65 | }) 66 | } 67 | } 68 | 69 | // Recover is a middleware that recovers from panics in the delegate 70 | // handler and prints a stack trace. 71 | func Recover() Middleware { 72 | return func(h http.Handler) http.Handler { 73 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 74 | defer func() { 75 | if err := recover(); err != nil { 76 | http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 77 | slog.Error(r.RequestURI, fmt.Errorf(`panic("%s")`, err)) 78 | fmt.Println(string(debug.Stack())) 79 | } 80 | }() 81 | h.ServeHTTP(w, r) 82 | }) 83 | } 84 | } 85 | 86 | // RequestSize limits the size of incoming request bodies. 87 | func RequestSize(n int64) Middleware { 88 | return func(h http.Handler) http.Handler { 89 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 90 | r.Body = http.MaxBytesReader(w, r.Body, n) 91 | h.ServeHTTP(w, r) 92 | }) 93 | } 94 | } 95 | 96 | type statusRecorder struct { 97 | http.ResponseWriter 98 | status int 99 | } 100 | 101 | func (rec *statusRecorder) WriteHeader(code int) { 102 | rec.status = code 103 | rec.ResponseWriter.WriteHeader(code) 104 | } 105 | 106 | // Timeout returns a new Middleware that times out each request after the given 107 | // duration. 108 | func Timeout(d time.Duration) Middleware { 109 | return func(h http.Handler) http.Handler { 110 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 111 | http.TimeoutHandler(h, d, "request timed out").ServeHTTP(w, r) 112 | }) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /internal/upload/findwork.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package upload 6 | 7 | import ( 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | // files to handle 14 | type work struct { 15 | // absolute file names 16 | countfiles []string // count files to process 17 | readyfiles []string // old reports to upload 18 | // relative names 19 | uploaded map[string]bool // reports that have been uploaded 20 | } 21 | 22 | // find all the files that look like counter files or reports 23 | // that need to be uploaded. (There may be unexpected leftover files 24 | // and uploading is supposed to be idempotent.) 25 | func (u *uploader) findWork() work { 26 | localdir, uploaddir := u.dir.LocalDir(), u.dir.UploadDir() 27 | var ans work 28 | fis, err := os.ReadDir(localdir) 29 | if err != nil { 30 | u.logger.Printf("Could not find work: failed to read local dir %s: %v", localdir, err) 31 | return ans 32 | } 33 | 34 | mode, asof := u.dir.Mode() 35 | u.logger.Printf("Finding work: mode %s asof %s", mode, asof) 36 | 37 | // count files end in .v1.count 38 | // reports end in .json. If they are not to be uploaded they 39 | // start with local. 40 | for _, fi := range fis { 41 | if strings.HasSuffix(fi.Name(), ".v1.count") { 42 | fname := filepath.Join(localdir, fi.Name()) 43 | _, expiry, err := u.counterDateSpan(fname) 44 | switch { 45 | case err != nil: 46 | u.logger.Printf("Error reading expiry for count file %s: %v", fi.Name(), err) 47 | case expiry.After(u.startTime): 48 | u.logger.Printf("Skipping count file %s: still active", fi.Name()) 49 | default: 50 | u.logger.Printf("Collecting count file %s", fi.Name()) 51 | ans.countfiles = append(ans.countfiles, fname) 52 | } 53 | } else if strings.HasPrefix(fi.Name(), "local.") { 54 | // skip 55 | } else if strings.HasSuffix(fi.Name(), ".json") && mode == "on" { 56 | // Collect reports that are ready for upload. 57 | reportDate := u.uploadReportDate(fi.Name()) 58 | if !asof.IsZero() && !reportDate.IsZero() { 59 | // If both the mode asof date and the report date are present, do the 60 | // right thing... 61 | // 62 | // (see https://github.com/golang/go/issues/63142#issuecomment-1734025130) 63 | if asof.Before(reportDate) { 64 | // Note: since this report was created after telemetry was enabled, 65 | // we can only assume that the process that created it checked that 66 | // the counter data contained therein was all from after the asof 67 | // date. 68 | // 69 | // TODO(rfindley): store the begin date in reports, so that we can 70 | // verify this assumption. 71 | u.logger.Printf("Uploadable: %s", fi.Name()) 72 | ans.readyfiles = append(ans.readyfiles, filepath.Join(localdir, fi.Name())) 73 | } 74 | } else { 75 | // ...otherwise fall back on the old behavior of uploading all 76 | // unuploaded files. 77 | // 78 | // TODO(rfindley): invert this logic following more testing. We 79 | // should only upload if we know both the asof date and the report 80 | // date, and they are acceptable. 81 | u.logger.Printf("Uploadable (missing date): %s", fi.Name()) 82 | ans.readyfiles = append(ans.readyfiles, filepath.Join(localdir, fi.Name())) 83 | } 84 | } 85 | } 86 | 87 | fis, err = os.ReadDir(uploaddir) 88 | if err != nil { 89 | os.MkdirAll(uploaddir, 0777) 90 | return ans 91 | } 92 | // There should be only one of these per day; maybe sometime 93 | // we'll want to clean the directory. 94 | ans.uploaded = make(map[string]bool) 95 | for _, fi := range fis { 96 | if strings.HasSuffix(fi.Name(), ".json") { 97 | u.logger.Printf("Already uploaded: %s", fi.Name()) 98 | ans.uploaded[fi.Name()] = true 99 | } 100 | } 101 | return ans 102 | } 103 | -------------------------------------------------------------------------------- /cmd/gotelemetry/internal/csv/csv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // csv dumps all the active counters. The output is 6 | // a sequence of lines 7 | // value,"counter-name",program, version,go-version,goos, garch 8 | // sorted by counter name. It looks at the files in 9 | // telemetry.LocalDir that are counter files or local reports 10 | // By design it pays no attention to dates. The combination 11 | // of program version and go version are deemed sufficient. 12 | package csv 13 | 14 | import ( 15 | "encoding/json" 16 | "fmt" 17 | "log" 18 | "os" 19 | "path/filepath" 20 | "sort" 21 | "strings" 22 | 23 | "golang.org/x/telemetry/internal/counter" 24 | "golang.org/x/telemetry/internal/telemetry" 25 | ) 26 | 27 | type file struct { 28 | path, name string 29 | // one of counters or report is set 30 | counters *counter.File 31 | report *telemetry.Report 32 | } 33 | 34 | func Csv() { 35 | files, err := readdir(telemetry.Default.LocalDir(), nil) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | for _, f := range files { 40 | if strings.HasSuffix(f.name, "v1.count") { 41 | buf, err := os.ReadFile(f.path) 42 | if err != nil { 43 | log.Print(err) 44 | continue 45 | } 46 | cf, err := counter.Parse(f.name, buf) 47 | if err != nil { 48 | log.Print(err) 49 | continue 50 | } 51 | f.counters = cf 52 | } else if strings.HasSuffix(f.name, ".json") { 53 | buf, err := os.ReadFile(f.path) 54 | if err != nil { 55 | log.Print(err) 56 | continue 57 | } 58 | var x telemetry.Report 59 | if err := json.Unmarshal(buf, &x); err != nil { 60 | log.Print(err) 61 | continue 62 | } 63 | f.report = &x 64 | } 65 | } 66 | printTable(files) 67 | } 68 | 69 | type record struct { 70 | goos, garch, program, version, goversion string 71 | cntr string 72 | count int 73 | } 74 | 75 | func printTable(files []*file) { 76 | lines := make(map[string]*record) 77 | work := func(k string, v int64, rec *record) { 78 | x, ok := lines[k] 79 | if !ok { 80 | x = new(record) 81 | *x = *rec 82 | x.cntr = k 83 | } 84 | x.count += int(v) 85 | lines[k] = x 86 | } 87 | worku := func(k string, v uint64, rec *record) { 88 | work(k, int64(v), rec) 89 | } 90 | for _, f := range files { 91 | if f.counters != nil { 92 | var rec record 93 | rec.goos = f.counters.Meta["GOOS"] 94 | rec.garch = f.counters.Meta["GOARCH"] 95 | rec.program = f.counters.Meta["Program"] 96 | rec.version = f.counters.Meta["Version"] 97 | rec.goversion = f.counters.Meta["GoVersion"] 98 | for k, v := range f.counters.Count { 99 | worku(k, v, &rec) 100 | } 101 | } else if f.report != nil { 102 | for _, p := range f.report.Programs { 103 | var rec record 104 | rec.goos = p.GOOS 105 | rec.garch = p.GOARCH 106 | rec.goversion = p.GoVersion 107 | rec.program = p.Program 108 | rec.version = p.Version 109 | for k, v := range p.Counters { 110 | work(k, v, &rec) 111 | } 112 | for k, v := range p.Stacks { 113 | work(k, v, &rec) 114 | } 115 | } 116 | } 117 | } 118 | keys := make([]string, 0, len(lines)) 119 | for k := range lines { 120 | keys = append(keys, k) 121 | } 122 | sort.Strings(keys) 123 | for _, k := range keys { 124 | printRecord(lines[k]) 125 | } 126 | } 127 | 128 | func printRecord(r *record) { 129 | fmt.Printf("%d,%q,%s,%s,%s,%s,%s\n", r.count, r.cntr, r.program, 130 | r.version, r.goversion, r.goos, r.garch) 131 | } 132 | 133 | func readdir(dir string, files []*file) ([]*file, error) { 134 | fi, err := os.ReadDir(dir) 135 | if err != nil { 136 | return nil, err 137 | } 138 | for _, f := range fi { 139 | files = append(files, &file{path: filepath.Join(dir, f.Name()), name: f.Name()}) 140 | } 141 | return files, nil 142 | } 143 | -------------------------------------------------------------------------------- /internal/upload/upload.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package upload 6 | 7 | import ( 8 | "bytes" 9 | "net/http" 10 | "os" 11 | "path/filepath" 12 | "regexp" 13 | "strings" 14 | "time" 15 | 16 | "golang.org/x/telemetry/internal/telemetry" 17 | ) 18 | 19 | var ( 20 | dateRE = regexp.MustCompile(`(\d\d\d\d-\d\d-\d\d)[.]json$`) 21 | dateFormat = telemetry.DateOnly 22 | // TODO(rfindley): use dateFormat throughout. 23 | ) 24 | 25 | // uploadReportDate returns the date component of the upload file name, or "" if the 26 | // date was unmatched. 27 | func (u *uploader) uploadReportDate(fname string) time.Time { 28 | match := dateRE.FindStringSubmatch(fname) 29 | if match == nil || len(match) < 2 { 30 | u.logger.Printf("malformed report name: missing date: %q", filepath.Base(fname)) 31 | return time.Time{} 32 | } 33 | d, err := time.Parse(dateFormat, match[1]) 34 | if err != nil { 35 | u.logger.Printf("malformed report name: bad date: %q", filepath.Base(fname)) 36 | return time.Time{} 37 | } 38 | return d 39 | } 40 | 41 | func (u *uploader) uploadReport(fname string) { 42 | thisInstant := u.startTime 43 | // TODO(rfindley): use uploadReportDate here, once we've done a gopls release. 44 | 45 | // first make sure it is not in the future 46 | today := thisInstant.Format(telemetry.DateOnly) 47 | match := dateRE.FindStringSubmatch(fname) 48 | if match == nil || len(match) < 2 { 49 | u.logger.Printf("Report name %q missing date", filepath.Base(fname)) 50 | } else if match[1] > today { 51 | u.logger.Printf("Report date for %q is later than today (%s)", filepath.Base(fname), today) 52 | return // report is in the future, which shouldn't happen 53 | } 54 | buf, err := os.ReadFile(fname) 55 | if err != nil { 56 | u.logger.Printf("%v reading %s", err, fname) 57 | return 58 | } 59 | if u.uploadReportContents(fname, buf) { 60 | // anything left to do? 61 | } 62 | } 63 | 64 | // try to upload the report, 'true' if successful 65 | func (u *uploader) uploadReportContents(fname string, buf []byte) bool { 66 | fdate := strings.TrimSuffix(filepath.Base(fname), ".json") 67 | fdate = fdate[len(fdate)-len(telemetry.DateOnly):] 68 | 69 | newname := filepath.Join(u.dir.UploadDir(), fdate+".json") 70 | 71 | // Lock the upload, to prevent duplicate uploads. 72 | { 73 | lockname := newname + ".lock" 74 | lockfile, err := os.OpenFile(lockname, os.O_CREATE|os.O_EXCL, 0666) 75 | if err != nil { 76 | u.logger.Printf("Failed to acquire lock %s: %v", lockname, err) 77 | return false 78 | } 79 | _ = lockfile.Close() 80 | defer os.Remove(lockname) 81 | } 82 | 83 | if _, err := os.Stat(newname); err == nil { 84 | // Another process uploaded but failed to clean up (or hasn't yet cleaned 85 | // up). Ensure that cleanup occurs. 86 | u.logger.Printf("After acquire: report already uploaded") 87 | _ = os.Remove(fname) 88 | return false 89 | } 90 | 91 | endpoint := u.uploadServerURL + "/" + fdate 92 | b := bytes.NewReader(buf) 93 | resp, err := http.Post(endpoint, "application/json", b) 94 | if err != nil { 95 | u.logger.Printf("Error upload %s to %s: %v", filepath.Base(fname), endpoint, err) 96 | return false 97 | } 98 | // hope for a 200, remove file on a 4xx, otherwise it will be retried by another process 99 | if resp.StatusCode != 200 { 100 | u.logger.Printf("Failed to upload %s to %s: %s", filepath.Base(fname), endpoint, resp.Status) 101 | if resp.StatusCode >= 400 && resp.StatusCode < 500 { 102 | err := os.Remove(fname) 103 | if err == nil { 104 | u.logger.Printf("Removed local/%s", filepath.Base(fname)) 105 | } else { 106 | u.logger.Printf("Error removing local/%s: %v", filepath.Base(fname), err) 107 | } 108 | } 109 | return false 110 | } 111 | // Store a copy of the uploaded report in the uploaded directory. 112 | if err := os.WriteFile(newname, buf, 0644); err == nil { 113 | os.Remove(fname) // if it exists 114 | } 115 | u.logger.Printf("Uploaded %s to %q", fdate+".json", endpoint) 116 | return true 117 | } 118 | -------------------------------------------------------------------------------- /internal/content/telemetrygodev/privacy.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Go Telemetry Privacy Policy 3 | Layout: privacy.html 4 | --- 5 | 6 | # Privacy Policy 7 | 8 | _Last updated: January 24, 2024_ 9 | 10 | Go Telemetry is a way for Go toolchain programs to collect data about their 11 | performance and usage. This data can help developers improve the language and 12 | tools. 13 | 14 | ## What Go Telemetry Records {#collection} 15 | 16 | Go toolchain programs, such as the `go` command and `gopls`, record certain information 17 | about their own execution. This data is stored in local files on your computer, 18 | specifically in the [`os.UserConfigDir()/go/telemetry/local`](https://pkg.go.dev/os#UserConfigDir) directory. 19 | 20 | Here is what these files contain: 21 | 22 | * Event counters: Information about how Go toolchain programs 23 | are used. 24 | * Stack traces: Details about program execution for troubleshooting. 25 | * Basic system information: Your operating system, CPU architecture, and name and version of the Go tool being executed. 26 | 27 | Importantly, these files do not contain personal or other 28 | identifying information about you or your system. 29 | 30 | ## Data Privacy {#data-privacy} 31 | 32 | By default, the data collected by Go Telemetry is kept only locally on your computer. 33 | 34 | It is not shared with anyone unless you explicitly decide to enable Go Telemetry. 35 | You can do this by running the command [`gotelemetry on`](#command) or using a command 36 | in your integrated development environment (IDE). 37 | 38 | Once enabled, Go Telemetry may decide once a week to upload reports to a Google 39 | server. A local copy of the uploaded reports is kept in the 40 | [`os.UserConfigDir()/go/telemetry/remote`](https://pkg.go.dev/os#UserConfigDir) directory on the user's machine. 41 | These reports include only approved counters and are collected in 42 | accordance with the Google Privacy Policy, which you can find 43 | at [Google Privacy Policy](https://policies.google.com/privacy). 44 | 45 | The uploaded reports are also made available as part of a public dataset at 46 | [telemetry.go.dev](https://telemetry.go.dev). Developers working on Go, 47 | both inside and outside of Google, use this dataset to understand 48 | how the Go toolchain is used and if it is performing as expected. 49 | 50 | ## Using the `gotelemetry` Command Line Tool {#command} 51 | 52 | To manage Go Telemetry, you can use the `gotelemetry` command line tool. 53 | 54 | go install golang.org/x/telemetry/cmd/gotelemetry@latest 55 | 56 | Here are some useful commands: 57 | 58 | * `gotelemetry on`: Upload Go Telemetry data weekly. 59 | * `gotelemetry off`: Do not upload Go Telemetry data. 60 | * `gotelemetry view`: View locally collected telemetry data. 61 | * `gotelemetry clear`: Clear locally collected telemetry data at any time. 62 | 63 | For the complete usage documentation of the gotelemetry command line tool, visit 64 | [golang.org/x/telemetry/cmd/gotelemetry](https://golang.org/x/telemetry/cmd/gotelemetry). 65 | 66 | 67 | ## Approved Counters {#config} 68 | 69 | Go Telemetry only uploads counters that have been approved through the [public proposal process](https://github.com/orgs/golang/projects/29). 70 | You can find the set of approved counters as a Go module at 71 | [golang.org/x/telemetry/config](https://go.googlesource.com/telemetry/+/refs/heads/master/config/config.json) and the [current config in use](https://telemetry.go.dev/config). 72 | 73 | ## IDE Integration {#integration} 74 | 75 | If you're using an integrated development environment (IDE) like Visual Studio 76 | Code, versions 77 | [`v0.14.0`](https://github.com/golang/tools/releases/tag/gopls%2Fv0.14.0) and 78 | later of the Go language server [gopls](https://go.dev/s/gopls) collect 79 | telemetry data. As described above, data is only uploaded after you have opted 80 | in, either by using the command [`gotelemetry on`](#command) as described above 81 | or by accepting a dialog in the IDE. 82 | 83 | You can always opt out of uploading at any time by using the 84 | [`gotelemetry local`](#command) or [`gotelemetry off`](#command) commands. 85 | 86 | By sharing performance statistics, usage information, and crash reports with Go Telemetry, 87 | you can help improve the Go programming language and its tools while also ensuring 88 | your data privacy. 89 | -------------------------------------------------------------------------------- /godev/internal/storage/storage_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package storage 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "errors" 11 | "testing" 12 | 13 | "github.com/google/go-cmp/cmp" 14 | ) 15 | 16 | type jsondata struct { 17 | Tars string 18 | Case string 19 | Kipp map[string]int 20 | } 21 | 22 | var writeData = jsondata{ 23 | Tars: "foo", 24 | Case: "bar", 25 | Kipp: map[string]int{"plex": 0}, 26 | } 27 | 28 | func TestFSStore(t *testing.T) { 29 | ctx := context.Background() 30 | s, err := NewFSBucket(ctx, t.TempDir(), "test-bucket") 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | runTest(t, ctx, s) 35 | } 36 | 37 | func runTest(t *testing.T, ctx context.Context, s BucketHandle) { 38 | // write the object to store 39 | if err := write(ctx, s, "prefix/test-object", writeData); err != nil { 40 | t.Fatal(err) 41 | } 42 | // read same object from store 43 | readData, err := read(ctx, s, "prefix/test-object") 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | if diff := cmp.Diff(writeData, readData); diff != "" { 48 | t.Errorf("data write read mismatch (-wrote +read):\n%s", diff) 49 | } 50 | 51 | // write an object with a different prefix to store 52 | if err = write(ctx, s, "other-prefix/test-object-2", writeData); err != nil { 53 | t.Fatal(err) 54 | } 55 | // check that prefix matches single object 56 | it := s.Objects(ctx, "prefix") 57 | var list1 []string 58 | for { 59 | elem, err := it.Next() 60 | if errors.Is(err, ErrObjectIteratorDone) { 61 | break 62 | } 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | list1 = append(list1, elem) 67 | } 68 | if diff := cmp.Diff(list1, []string{"prefix/test-object"}); diff != "" { 69 | t.Errorf("Objects() mismatch (-want +got):\n%s", diff) 70 | } 71 | 72 | // check that prefix matches with partial path and separator 73 | it = s.Objects(ctx, "prefix/test") 74 | var list2 []string 75 | for { 76 | elem, err := it.Next() 77 | if errors.Is(err, ErrObjectIteratorDone) { 78 | break 79 | } 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | list2 = append(list2, elem) 84 | } 85 | 86 | if diff := cmp.Diff(list2, []string{"prefix/test-object"}); diff != "" { 87 | t.Errorf("Objects() mismatch (-want +got):\n%s", diff) 88 | } 89 | 90 | // check that the destination file have same content as source. 91 | copyData := jsondata{"foo", "bar", map[string]int{"copy": 1}} 92 | if err := write(ctx, s, "prefix/source-file", copyData); err != nil { 93 | t.Fatal(err) 94 | } 95 | if err := Copy(ctx, s.Object("prefix/dest-file"), s.Object("prefix/source-file")); err != nil { 96 | t.Errorf("Copy() should not return err: %v", err) 97 | } 98 | got, err := read(ctx, s, "prefix/dest-file") 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | if diff := cmp.Diff(copyData, got); diff != "" { 103 | t.Errorf("data write read mismatch (-wrote +read):\n%s", diff) 104 | } 105 | 106 | // check that copy twice have same result. 107 | if err := Copy(ctx, s.Object("prefix/dest-file"), s.Object("prefix/source-file")); err != nil { 108 | t.Errorf("Copy() should not return err: %v", err) 109 | } 110 | got, err = read(ctx, s, "prefix/dest-file") 111 | if err != nil { 112 | t.Fatal(err) 113 | } 114 | if diff := cmp.Diff(copyData, got); diff != "" { 115 | t.Errorf("data write read mismatch (-wrote +read):\n%s", diff) 116 | } 117 | } 118 | 119 | func write(ctx context.Context, s BucketHandle, object string, data any) error { 120 | obj, err := s.Object(object).NewWriter(ctx) 121 | if err != nil { 122 | return err 123 | } 124 | if err := json.NewEncoder(obj).Encode(data); err != nil { 125 | return err 126 | } 127 | return obj.Close() 128 | } 129 | 130 | func read(ctx context.Context, s BucketHandle, object string) (any, error) { 131 | obj, err := s.Object(object).NewReader(ctx) 132 | if err != nil { 133 | return nil, err 134 | } 135 | var data jsondata 136 | if err := json.NewDecoder(obj).Decode(&data); err != nil { 137 | return nil, err 138 | } 139 | if err := obj.Close(); err != nil { 140 | return nil, err 141 | } 142 | return data, nil 143 | } 144 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // package config provides methods for loading and querying a 6 | // telemetry upload config file. 7 | package config 8 | 9 | import ( 10 | "encoding/json" 11 | "os" 12 | "strings" 13 | 14 | "golang.org/x/telemetry/internal/telemetry" 15 | ) 16 | 17 | // Config is a wrapper around telemetry.UploadConfig that provides some 18 | // convenience methods for checking the contents of a report. 19 | type Config struct { 20 | *telemetry.UploadConfig 21 | program map[string]bool 22 | goos map[string]bool 23 | goarch map[string]bool 24 | goversion map[string]bool 25 | pgversion map[pgkey]bool 26 | pgcounter map[pgkey]bool 27 | pgcounterprefix map[pgkey]bool 28 | pgstack map[pgkey]bool 29 | rate map[pgkey]float64 30 | } 31 | 32 | type pgkey struct { 33 | program, key string 34 | } 35 | 36 | func ReadConfig(file string) (*Config, error) { 37 | data, err := os.ReadFile(file) 38 | if err != nil { 39 | return nil, err 40 | } 41 | var cfg telemetry.UploadConfig 42 | if err := json.Unmarshal(data, &cfg); err != nil { 43 | return nil, err 44 | } 45 | return NewConfig(&cfg), nil 46 | } 47 | 48 | func NewConfig(cfg *telemetry.UploadConfig) *Config { 49 | ucfg := Config{UploadConfig: cfg} 50 | ucfg.goos = set(ucfg.GOOS) 51 | ucfg.goarch = set(ucfg.GOARCH) 52 | ucfg.goversion = set(ucfg.GoVersion) 53 | ucfg.program = make(map[string]bool, len(ucfg.Programs)) 54 | ucfg.pgversion = make(map[pgkey]bool, len(ucfg.Programs)) 55 | ucfg.pgcounter = make(map[pgkey]bool, len(ucfg.Programs)) 56 | ucfg.pgcounterprefix = make(map[pgkey]bool, len(ucfg.Programs)) 57 | ucfg.pgstack = make(map[pgkey]bool, len(ucfg.Programs)) 58 | ucfg.rate = make(map[pgkey]float64) 59 | for _, p := range ucfg.Programs { 60 | ucfg.program[p.Name] = true 61 | for _, v := range p.Versions { 62 | ucfg.pgversion[pgkey{p.Name, v}] = true 63 | } 64 | for _, c := range p.Counters { 65 | for _, e := range Expand(c.Name) { 66 | ucfg.pgcounter[pgkey{p.Name, e}] = true 67 | ucfg.rate[pgkey{p.Name, e}] = c.Rate 68 | } 69 | prefix, _, found := strings.Cut(c.Name, ":") 70 | if found { 71 | ucfg.pgcounterprefix[pgkey{p.Name, prefix}] = true 72 | } 73 | } 74 | for _, s := range p.Stacks { 75 | ucfg.pgstack[pgkey{p.Name, s.Name}] = true 76 | ucfg.rate[pgkey{p.Name, s.Name}] = s.Rate 77 | } 78 | } 79 | return &ucfg 80 | } 81 | 82 | func (r *Config) HasProgram(s string) bool { 83 | return r.program[s] 84 | } 85 | 86 | func (r *Config) HasGOOS(s string) bool { 87 | return r.goos[s] 88 | } 89 | 90 | func (r *Config) HasGOARCH(s string) bool { 91 | return r.goarch[s] 92 | } 93 | 94 | func (r *Config) HasGoVersion(s string) bool { 95 | return r.goversion[s] 96 | } 97 | 98 | func (r *Config) HasVersion(program, version string) bool { 99 | return r.pgversion[pgkey{program, version}] 100 | } 101 | 102 | func (r *Config) HasCounter(program, counter string) bool { 103 | return r.pgcounter[pgkey{program, counter}] 104 | } 105 | 106 | func (r *Config) HasCounterPrefix(program, prefix string) bool { 107 | return r.pgcounterprefix[pgkey{program, prefix}] 108 | } 109 | 110 | func (r *Config) HasStack(program, stack string) bool { 111 | return r.pgstack[pgkey{program, stack}] 112 | } 113 | 114 | func (r *Config) Rate(program, name string) float64 { 115 | return r.rate[pgkey{program, name}] 116 | } 117 | 118 | func set(slice []string) map[string]bool { 119 | s := make(map[string]bool, len(slice)) 120 | for _, v := range slice { 121 | s[v] = true 122 | } 123 | return s 124 | } 125 | 126 | // Expand takes a counter defined with buckets and expands it into distinct 127 | // strings for each bucket. 128 | func Expand(counter string) []string { 129 | prefix, rest, hasBuckets := strings.Cut(counter, "{") 130 | var counters []string 131 | if hasBuckets { 132 | buckets := strings.Split(strings.TrimSuffix(rest, "}"), ",") 133 | for _, b := range buckets { 134 | counters = append(counters, prefix+b) 135 | } 136 | } else { 137 | counters = append(counters, prefix) 138 | } 139 | return counters 140 | } 141 | -------------------------------------------------------------------------------- /internal/content/shared/static/treenav.min.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../treenav.ts"], 4 | "sourcesContent": ["/**\n * @license\n * Copyright 2024 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n/**\n * A treeNavController adds dynamic expansion and selection of index list\n * elements based on scroll position.\n *\n * Use it as follows:\n * - Add the .js-Tree class to a parent element of your index and content.\n * - Add the .js-Tree-item class to
  • elements of your index.\n * - Add the .js-Tree-heading class to heading elements of your content.\n *\n * Then, when you scroll content, the 'aria-selected' and 'aria-expanded'\n * attributes of your tree items will be set according to the current content\n * scroll position. The included treenav.css implements styling to expand and\n * highlight index elements according to these attributes.\n */\nexport function treeNavController(el: HTMLElement) {\n const headings = el.querySelectorAll(\".js-Tree-heading\");\n const callback = () => {\n // Collect heading elements above the scroll position.\n let above: HTMLHeadingElement[] = [];\n for (const h of headings) {\n const rect = h.getBoundingClientRect();\n if (rect.height && rect.top < 80) {\n above.unshift(h);\n }\n }\n // Highlight the first heading even if we're not yet scrolled below it.\n if (above.length == 0 && headings[0] instanceof HTMLHeadingElement) {\n above = [headings[0]];\n }\n // Collect the set of heading levels we're immediately below, at most one\n // per heading level, by decresing level.\n // e.g. [

    ,

    ,

    ]\n let threshold = Infinity;\n const active: HTMLHeadingElement[] = [];\n for (const h of above) {\n const level = Number(h.tagName[1]);\n if (level < threshold) {\n threshold = level;\n active.push(h);\n }\n }\n // Update aria-selected and aria-expanded for all items, per the current\n // position.\n const navItems = el.querySelectorAll(\".js-Tree-item\");\n for (const item of navItems) {\n const headingId = item.dataset[\"headingId\"];\n let selected = false,\n expanded = false;\n for (const h of active) {\n if (h.id === headingId) {\n if (h === active[0]) {\n selected = true;\n } else {\n expanded = true;\n }\n break;\n }\n }\n item.setAttribute(\"aria-selected\", selected ? \"true\" : \"false\");\n item.setAttribute(\"aria-expanded\", expanded ? \"true\" : \"false\");\n }\n };\n\n // Update on changes to viewport intersection, defensively debouncing to\n // guard against performance issues.\n const observer = new IntersectionObserver(debounce(callback, 20));\n for (const h of headings) {\n observer.observe(h);\n }\n}\n\nexport function debounce unknown>(\n callback: T,\n wait: number\n) {\n let timeout: number;\n return (...args: unknown[]) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => callback(...args), wait);\n };\n}\n"], 5 | "mappings": ";mBAqBO,SAASA,EAAkBC,EAAiB,CACjD,IAAMC,EAAWD,EAAG,iBAAqC,kBAAkB,EACrEE,EAAW,IAAM,CAErB,IAAIC,EAA8B,CAAC,EACnC,QAAWC,KAAKH,EAAU,CACxB,IAAMI,EAAOD,EAAE,sBAAsB,EACjCC,EAAK,QAAUA,EAAK,IAAM,IAC5BF,EAAM,QAAQC,CAAC,EAIfD,EAAM,QAAU,GAAKF,EAAS,CAAC,YAAa,qBAC9CE,EAAQ,CAACF,EAAS,CAAC,CAAC,GAKtB,IAAIK,EAAY,IACVC,EAA+B,CAAC,EACtC,QAAWH,KAAKD,EAAO,CACrB,IAAMK,EAAQ,OAAOJ,EAAE,QAAQ,CAAC,CAAC,EAC7BI,EAAQF,IACVA,EAAYE,EACZD,EAAO,KAAKH,CAAC,GAKjB,IAAMK,EAAWT,EAAG,iBAA8B,eAAe,EACjE,QAAWU,KAAQD,EAAU,CAC3B,IAAME,EAAYD,EAAK,QAAQ,UAC3BE,EAAW,GACbC,EAAW,GACb,QAAWT,KAAKG,EACd,GAAIH,EAAE,KAAOO,EAAW,CAClBP,IAAMG,EAAO,CAAC,EAChBK,EAAW,GAEXC,EAAW,GAEb,MAGJH,EAAK,aAAa,gBAAiBE,EAAW,OAAS,OAAO,EAC9DF,EAAK,aAAa,gBAAiBG,EAAW,OAAS,OAAO,EAElE,EAIMC,EAAW,IAAI,qBAAqBC,EAASb,EAAU,EAAE,CAAC,EAChE,QAAWE,KAAKH,EACda,EAAS,QAAQV,CAAC,CAEtB,CAEO,SAASW,EACdb,EACAc,EACA,CACA,IAAIC,EACJ,MAAO,IAAIC,IAAoB,CAC7B,aAAaD,CAAO,EACpBA,EAAU,WAAW,IAAMf,EAAS,GAAGgB,CAAI,EAAGF,CAAI,CACpD,CACF", 6 | "names": ["treeNavController", "el", "headings", "callback", "above", "h", "rect", "threshold", "active", "level", "navItems", "item", "headingId", "selected", "expanded", "observer", "debounce", "wait", "timeout", "args"] 7 | } 8 | -------------------------------------------------------------------------------- /internal/content/gotelemetryview/index.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2023 The Go Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | @import url("../shared/base.css"); 8 | 9 | html { 10 | scroll-padding-top: 4rem; 11 | } 12 | 13 | /* TODO(rfindley): refactor to share breadcrumb logic with telemetry.go.dev */ 14 | .ViewBreadcrumb { 15 | position: sticky; 16 | top: 0; 17 | z-index: 1000; 18 | } 19 | 20 | .ViewBreadcrumb ol { 21 | align-items: center; 22 | border-bottom: var(--border); 23 | display: inline-flex; 24 | gap: 1rem; 25 | list-style: none; 26 | margin-block-start: 0; 27 | margin-block-end: 0; 28 | padding-inline-start: 0; 29 | min-height: 3rem; 30 | width: calc(100% - 2rem); 31 | background-color: var(--color-background); 32 | padding: 0 1rem; 33 | font-size: 0.875rem; 34 | position: fixed; 35 | top: 0; 36 | transition: top 0.1s ease-in 0.1s; 37 | } 38 | 39 | .ViewBreadcrumb ol:empty { 40 | top: -3.0625rem; 41 | } 42 | 43 | .ViewBreadcrumb li:not(:last-child)::after { 44 | content: ">"; 45 | margin-left: 1rem; 46 | } 47 | 48 | .ViewBreadcrumb li:last-child a { 49 | color: var(--color-text-subtle); 50 | } 51 | 52 | .Index { 53 | line-height: 1.5; 54 | } 55 | 56 | .Counters { 57 | border: var(--border); 58 | border-radius: 0.25rem; 59 | display: grid; 60 | gap: 1rem 2rem; 61 | margin-top: 1rem; 62 | overflow: auto; 63 | padding: 1rem; 64 | grid-template-areas: 65 | "meta count count" 66 | "stack stack stack" 67 | "summary summary summary"; 68 | grid-auto-columns: 1fr 2fr 1fr; 69 | } 70 | 71 | .Meta { 72 | grid-area: meta; 73 | display: grid; 74 | grid-auto-rows: min-content; 75 | grid-template-columns: repeat(2, max-content); 76 | gap: 0.5rem; 77 | } 78 | 79 | .Stack { 80 | grid-area: stack; 81 | border-top: var(--border); 82 | padding-top: 1rem; 83 | gap: 0.5rem 1rem; 84 | display: flex; 85 | flex-direction: column; 86 | width: 100%; 87 | } 88 | 89 | .Stack summary { 90 | display: block; 91 | } 92 | 93 | .Stack details .Count-entry:first-child::before { 94 | content: "⏵"; 95 | } 96 | 97 | .Stack details[open] .Count-entry:first-child::before { 98 | content: "⏷"; 99 | } 100 | 101 | .Count { 102 | grid-area: count; 103 | display: grid; 104 | flex-grow: 1; 105 | grid-auto-rows: min-content; 106 | grid-template-columns: repeat(auto-fill, minmax(12.5rem, 1fr)); 107 | gap: 0.5rem 1rem; 108 | } 109 | 110 | .Summary { 111 | border-top: var(--border); 112 | font-size: 0.875rem; 113 | grid-area: summary; 114 | line-height: 1.5; 115 | padding-top: 1rem; 116 | } 117 | 118 | .Meta .unknown, 119 | .Count .unknown, 120 | .Stack .unknown { 121 | color: var(--color-text-subtle); 122 | } 123 | 124 | .Count-entry { 125 | display: flex; 126 | gap: 0.25rem; 127 | justify-content: space-between; 128 | } 129 | 130 | .Count-entry > span:nth-child(odd) { 131 | overflow: hidden; 132 | white-space: nowrap; 133 | } 134 | 135 | .Count-entry:not(.unknown) > span:nth-child(even) { 136 | text-align: right; 137 | color: var(--color-code-comment); 138 | } 139 | 140 | .Count-entry > span:nth-child(odd)::after { 141 | content: " ----------------------------------------------------------------------------------------------- "; 142 | letter-spacing: 0.125rem; 143 | } 144 | 145 | h2::after { 146 | content: "⏷"; 147 | padding-left: 0.5rem; 148 | } 149 | 150 | html[data-closed-sections*="index"] h2#index::after, 151 | html[data-closed-sections*="config"] h2#config::after, 152 | html[data-closed-sections*="files"] h2#files::after, 153 | html[data-closed-sections*="charts"] h2#charts::after, 154 | html[data-closed-sections*="reports"] h2#reports::after { 155 | content: "⏵"; 156 | } 157 | 158 | html[data-closed-sections*="index"] h2#index ~ *, 159 | html[data-closed-sections*="config"] h2#config ~ *, 160 | html[data-closed-sections*="files"] h2#files ~ *, 161 | html[data-closed-sections*="charts"] h2#charts ~ *, 162 | html[data-closed-sections*="reports"] h2#reports ~ * { 163 | display: none; 164 | } 165 | 166 | div[data-chart-id] { 167 | min-height: 16rem; 168 | } 169 | 170 | /* Fix tooltip background for dark theme */ 171 | svg g[aria-label="tip"] g { 172 | fill: var(--color-background); 173 | } 174 | --------------------------------------------------------------------------------