diff --git a/caddy.go b/caddy.go index 2e3235441..a29f05d79 100644 --- a/caddy.go +++ b/caddy.go @@ -44,6 +44,7 @@ import ( "time" "github.com/mholt/caddy/caddyfile" + "github.com/mholt/caddy/telemetry" ) // Configurable application parameters @@ -122,6 +123,7 @@ type Instance struct { StorageMu sync.RWMutex } +// Instances returns the list of instances. func Instances() []*Instance { return instances } @@ -615,6 +617,8 @@ func ValidateAndExecuteDirectives(cdyfile Input, inst *Instance, justValidate bo return fmt.Errorf("error inspecting server blocks: %v", err) } + telemetry.Set("num_server_blocks", len(sblocks)) + return executeDirectives(inst, cdyfile.Path(), stype.Directives(), sblocks, justValidate) } @@ -869,7 +873,7 @@ func Stop() error { // explicitly like a common local hostname. addr must only // be a host or a host:port combination. func IsLoopback(addr string) bool { - host, _, err := net.SplitHostPort(addr) + host, _, err := net.SplitHostPort(strings.ToLower(addr)) if err != nil { host = addr // happens if the addr is just a hostname } diff --git a/caddy/caddymain/run.go b/caddy/caddymain/run.go index ddc50aac0..96eb1a1e9 100644 --- a/caddy/caddymain/run.go +++ b/caddy/caddymain/run.go @@ -21,19 +21,20 @@ import ( "io/ioutil" "log" "os" + "path/filepath" "runtime" "strconv" "strings" + "github.com/google/uuid" + "github.com/klauspost/cpuid" + "github.com/mholt/caddy" + "github.com/mholt/caddy/caddytls" + "github.com/mholt/caddy/telemetry" + "github.com/xenolf/lego/acmev2" "gopkg.in/natefinch/lumberjack.v2" - "github.com/xenolf/lego/acmev2" - - "github.com/mholt/caddy" - // plug in the HTTP server type - _ "github.com/mholt/caddy/caddyhttp" - - "github.com/mholt/caddy/caddytls" + _ "github.com/mholt/caddy/caddyhttp" // plug in the HTTP server type // This is where other plugins get plugged in (imported) ) @@ -45,6 +46,7 @@ func init() { flag.StringVar(&caddytls.DefaultCAUrl, "ca", "https://acme-v02.api.letsencrypt.org/directory", "URL to certificate authority's ACME server directory") flag.BoolVar(&caddytls.DisableHTTPChallenge, "disable-http-challenge", caddytls.DisableHTTPChallenge, "Disable the ACME HTTP challenge") flag.BoolVar(&caddytls.DisableTLSSNIChallenge, "disable-tls-sni-challenge", caddytls.DisableTLSSNIChallenge, "Disable the ACME TLS-SNI challenge") + flag.StringVar(&disabledMetrics, "disabled-metrics", "", "Comma-separated list of telemetry metrics to disable") flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")") flag.StringVar(&cpu, "cpu", "100%", "CPU cap") flag.BoolVar(&plugins, "plugins", false, "List installed plugins") @@ -87,6 +89,16 @@ func Run() { }) } + // initialize telemetry client + if enableTelemetry { + err := initTelemetry() + if err != nil { + mustLogFatalf("[ERROR] Initializing telemetry: %v", err) + } + } else if disabledMetrics != "" { + mustLogFatalf("[ERROR] Cannot disable specific metrics because telemetry is disabled") + } + // Check for one-time actions if revoke != "" { err := caddytls.Revoke(revoke) @@ -143,6 +155,23 @@ func Run() { // Execute instantiation events caddy.EmitEvent(caddy.InstanceStartupEvent, instance) + // Begin telemetry (these are no-ops if telemetry disabled) + telemetry.Set("caddy_version", appVersion) + telemetry.Set("num_listeners", len(instance.Servers())) + telemetry.Set("server_type", serverType) + telemetry.Set("os", runtime.GOOS) + telemetry.Set("arch", runtime.GOARCH) + telemetry.Set("cpu", struct { + BrandName string `json:"brand_name,omitempty"` + NumLogical int `json:"num_logical,omitempty"` + AESNI bool `json:"aes_ni,omitempty"` + }{ + BrandName: cpuid.CPU.BrandName, + NumLogical: runtime.NumCPU(), + AESNI: cpuid.CPU.AesNi(), + }) + telemetry.StartEmitting() + // Twiddle your thumbs instance.Wait() } @@ -266,18 +295,86 @@ func setCPU(cpu string) error { return nil } +// initTelemetry initializes the telemetry engine. +func initTelemetry() error { + uuidFilename := filepath.Join(caddy.AssetsPath(), "uuid") + + newUUID := func() uuid.UUID { + id := uuid.New() + err := ioutil.WriteFile(uuidFilename, []byte(id.String()), 0600) // human-readable as a string + if err != nil { + log.Printf("[ERROR] Persisting instance UUID: %v", err) + } + return id + } + + var id uuid.UUID + + // load UUID from storage, or create one if we don't have one + if uuidFile, err := os.Open(uuidFilename); os.IsNotExist(err) { + // no UUID exists yet; create a new one and persist it + id = newUUID() + } else if err != nil { + log.Printf("[ERROR] Loading persistent UUID: %v", err) + id = newUUID() + } else { + defer uuidFile.Close() + uuidBytes, err := ioutil.ReadAll(uuidFile) + if err != nil { + log.Printf("[ERROR] Reading persistent UUID: %v", err) + id = newUUID() + } else { + id, err = uuid.ParseBytes(uuidBytes) + if err != nil { + log.Printf("[ERROR] Parsing UUID: %v", err) + id = newUUID() + } + } + } + + // parse and check the list of disabled metrics + var disabledMetricsSlice []string + if len(disabledMetrics) > 0 { + if len(disabledMetrics) > 1024 { + // mitigate disk space exhaustion at the collection endpoint + return fmt.Errorf("too many metrics to disable") + } + disabledMetricsSlice = strings.Split(disabledMetrics, ",") + for i, metric := range disabledMetricsSlice { + if metric == "instance_id" || metric == "timestamp" || metric == "disabled_metrics" { + return fmt.Errorf("instance_id, timestamp, and disabled_metrics cannot be disabled") + } + if metric == "" { + disabledMetricsSlice = append(disabledMetricsSlice[:i], disabledMetricsSlice[i+1:]...) + } + } + } + + // initialize telemetry + telemetry.Init(id, disabledMetricsSlice) + + // if any metrics were disabled, report it + if len(disabledMetricsSlice) > 0 { + telemetry.Set("disabled_metrics", disabledMetricsSlice) + log.Printf("[NOTICE] The following telemetry metrics are disabled: %s", disabledMetrics) + } + + return nil +} + const appName = "Caddy" // Flags that control program flow or startup var ( - serverType string - conf string - cpu string - logfile string - revoke string - version bool - plugins bool - validate bool + serverType string + conf string + cpu string + logfile string + revoke string + version bool + plugins bool + validate bool + disabledMetrics string ) // Build information obtained with the help of -ldflags @@ -292,3 +389,5 @@ var ( gitShortStat string // git diff-index --shortstat gitFilesModified string // git diff-index --name-only HEAD ) + +const enableTelemetry = true diff --git a/caddyfile/parse.go b/caddyfile/parse.go index d8252ff12..1c26a97f9 100644 --- a/caddyfile/parse.go +++ b/caddyfile/parse.go @@ -20,6 +20,8 @@ import ( "os" "path/filepath" "strings" + + "github.com/mholt/caddy/telemetry" ) // Parse parses the input just enough to group tokens, in @@ -374,6 +376,7 @@ func (p *parser) directive() error { // The directive itself is appended as a relevant token p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor]) + telemetry.AppendUnique("directives", dir) for p.Next() { if p.Val() == "{" { diff --git a/caddyhttp/httpserver/mitm.go b/caddyhttp/httpserver/mitm.go index 93209baa2..6744a924e 100644 --- a/caddyhttp/httpserver/mitm.go +++ b/caddyhttp/httpserver/mitm.go @@ -24,6 +24,9 @@ import ( "strconv" "strings" "sync" + + "github.com/mholt/caddy/caddytls" + "github.com/mholt/caddy/telemetry" ) // tlsHandler is a http.Handler that will inject a value @@ -49,6 +52,9 @@ type tlsHandler struct { // Halderman, et. al. in "The Security Impact of HTTPS Interception" (NDSS '17): // https://jhalderm.com/pub/papers/interception-ndss17.pdf func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // TODO: one request per connection, we should report UA in connection with + // handshake (reported in caddytls package) and our MITM assessment + if h.listener == nil { h.next.ServeHTTP(w, r) return @@ -60,6 +66,9 @@ func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ua := r.Header.Get("User-Agent") + // report this request's UA in connection with this ClientHello + go telemetry.AppendUnique("tls_client_hello_ua:"+caddytls.ClientHelloInfo(info).Key(), ua) + var checked, mitm bool if r.Header.Get("X-BlueCoat-Via") != "" || // Blue Coat (masks User-Agent header to generic values) r.Header.Get("X-FCCKV2") != "" || // Fortinet @@ -97,6 +106,13 @@ func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if checked { r = r.WithContext(context.WithValue(r.Context(), MitmCtxKey, mitm)) + if mitm { + go telemetry.AppendUnique("http_mitm", "likely") + } else { + go telemetry.AppendUnique("http_mitm", "unlikely") + } + } else { + go telemetry.AppendUnique("http_mitm", "unknown") } if mitm && h.closeOnMITM { @@ -195,6 +211,11 @@ func (c *clientHelloConn) Read(b []byte) (n int, err error) { c.listener.helloInfos[c.Conn.RemoteAddr().String()] = rawParsed c.listener.helloInfosMu.Unlock() + // report this ClientHello to telemetry + chKey := caddytls.ClientHelloInfo(rawParsed).Key() + go telemetry.SetNested("tls_client_hello", chKey, rawParsed) + go telemetry.AppendUnique("tls_client_hello_count", chKey) + c.readHello = true return } @@ -215,6 +236,7 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) { if len(data) < 42 { return } + info.Version = uint16(data[4])<<8 | uint16(data[5]) sessionIDLen := int(data[38]) if sessionIDLen > 32 || len(data) < 39+sessionIDLen { return @@ -231,9 +253,9 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) { } numCipherSuites := cipherSuiteLen / 2 // read in the cipher suites - info.cipherSuites = make([]uint16, numCipherSuites) + info.CipherSuites = make([]uint16, numCipherSuites) for i := 0; i < numCipherSuites; i++ { - info.cipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i]) + info.CipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i]) } data = data[2+cipherSuiteLen:] if len(data) < 1 { @@ -244,7 +266,7 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) { if len(data) < 1+compressionMethodsLen { return } - info.compressionMethods = data[1 : 1+compressionMethodsLen] + info.CompressionMethods = data[1 : 1+compressionMethodsLen] data = data[1+compressionMethodsLen:] @@ -272,7 +294,7 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) { } // record that the client advertised support for this extension - info.extensions = append(info.extensions, extension) + info.Extensions = append(info.Extensions, extension) switch extension { case extensionSupportedCurves: @@ -285,10 +307,10 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) { return } numCurves := l / 2 - info.curves = make([]tls.CurveID, numCurves) + info.Curves = make([]tls.CurveID, numCurves) d := data[2:] for i := 0; i < numCurves; i++ { - info.curves[i] = tls.CurveID(d[0])<<8 | tls.CurveID(d[1]) + info.Curves[i] = tls.CurveID(d[0])<<8 | tls.CurveID(d[1]) d = d[2:] } case extensionSupportedPoints: @@ -300,8 +322,8 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) { if length != l+1 { return } - info.points = make([]uint8, l) - copy(info.points, data[1:]) + info.Points = make([]uint8, l) + copy(info.Points, data[1:]) } data = data[length:] @@ -352,18 +374,12 @@ func (l *tlsHelloListener) Accept() (net.Conn, error) { // by Durumeric, Halderman, et. al. in // "The Security Impact of HTTPS Interception": // https://jhalderm.com/pub/papers/interception-ndss17.pdf -type rawHelloInfo struct { - cipherSuites []uint16 - extensions []uint16 - compressionMethods []byte - curves []tls.CurveID - points []uint8 -} +type rawHelloInfo caddytls.ClientHelloInfo // advertisesHeartbeatSupport returns true if info indicates // that the client supports the Heartbeat extension. func (info rawHelloInfo) advertisesHeartbeatSupport() bool { - for _, ext := range info.extensions { + for _, ext := range info.Extensions { if ext == extensionHeartbeat { return true } @@ -386,31 +402,31 @@ func (info rawHelloInfo) looksLikeFirefox() bool { // Note: Firefox 55+ doesn't appear to advertise 0xFF03 (65283, short headers). It used to be between 5 and 13. // Note: Firefox on Fedora (or RedHat) doesn't include ECC suites because of patent liability. requiredExtensionsOrder := []uint16{23, 65281, 10, 11, 35, 16, 5, 13} - if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) { + if !assertPresenceAndOrdering(requiredExtensionsOrder, info.Extensions, true) { return false } // We check for both presence of curves and their ordering. requiredCurves := []tls.CurveID{29, 23, 24, 25} - if len(info.curves) < len(requiredCurves) { + if len(info.Curves) < len(requiredCurves) { return false } for i := range requiredCurves { - if info.curves[i] != requiredCurves[i] { + if info.Curves[i] != requiredCurves[i] { return false } } - if len(info.curves) > len(requiredCurves) { + if len(info.Curves) > len(requiredCurves) { // newer Firefox (55 Nightly?) may have additional curves at end of list allowedCurves := []tls.CurveID{256, 257} for i := range allowedCurves { - if info.curves[len(requiredCurves)+i] != allowedCurves[i] { + if info.Curves[len(requiredCurves)+i] != allowedCurves[i] { return false } } } - if hasGreaseCiphers(info.cipherSuites) { + if hasGreaseCiphers(info.CipherSuites) { return false } @@ -437,7 +453,7 @@ func (info rawHelloInfo) looksLikeFirefox() bool { tls.TLS_RSA_WITH_AES_256_CBC_SHA, // 0x35 tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, // 0xa } - return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.cipherSuites, false) + return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.CipherSuites, false) } // looksLikeChrome returns true if info looks like a handshake @@ -478,20 +494,20 @@ func (info rawHelloInfo) looksLikeChrome() bool { TLS_DHE_RSA_WITH_AES_128_CBC_SHA: {}, // 0x33 TLS_DHE_RSA_WITH_AES_256_CBC_SHA: {}, // 0x39 } - for _, ext := range info.cipherSuites { + for _, ext := range info.CipherSuites { if _, ok := chromeCipherExclusions[ext]; ok { return false } } // Chrome does not include curve 25 (CurveP521) (as of Chrome 56, Feb. 2017). - for _, curve := range info.curves { + for _, curve := range info.Curves { if curve == 25 { return false } } - if !hasGreaseCiphers(info.cipherSuites) { + if !hasGreaseCiphers(info.CipherSuites) { return false } @@ -509,19 +525,19 @@ func (info rawHelloInfo) looksLikeEdge() bool { // More specifically, the OCSP status request extension appears // *directly* before the other two extensions, which occur in that // order. (I contacted the authors for clarification and verified it.) - for i, ext := range info.extensions { + for i, ext := range info.Extensions { if ext == extensionOCSPStatusRequest { - if len(info.extensions) <= i+2 { + if len(info.Extensions) <= i+2 { return false } - if info.extensions[i+1] != extensionSupportedCurves || - info.extensions[i+2] != extensionSupportedPoints { + if info.Extensions[i+1] != extensionSupportedCurves || + info.Extensions[i+2] != extensionSupportedPoints { return false } } } - for _, cs := range info.cipherSuites { + for _, cs := range info.CipherSuites { // As of Feb. 2017, Edge does not have 0xff, but Avast adds it if cs == scsvRenegotiation { return false @@ -532,7 +548,7 @@ func (info rawHelloInfo) looksLikeEdge() bool { } } - if hasGreaseCiphers(info.cipherSuites) { + if hasGreaseCiphers(info.CipherSuites) { return false } @@ -558,23 +574,23 @@ func (info rawHelloInfo) looksLikeSafari() bool { // We check for the presence and order of the extensions. requiredExtensionsOrder := []uint16{10, 11, 13, 13172, 16, 5, 18, 23} - if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) { + if !assertPresenceAndOrdering(requiredExtensionsOrder, info.Extensions, true) { // Safari on iOS 11 (beta) uses different set/ordering of extensions requiredExtensionsOrderiOS11 := []uint16{65281, 0, 23, 13, 5, 13172, 18, 16, 11, 10} - if !assertPresenceAndOrdering(requiredExtensionsOrderiOS11, info.extensions, true) { + if !assertPresenceAndOrdering(requiredExtensionsOrderiOS11, info.Extensions, true) { return false } } else { // For these versions of Safari, expect TLS_EMPTY_RENEGOTIATION_INFO_SCSV first. - if len(info.cipherSuites) < 1 { + if len(info.CipherSuites) < 1 { return false } - if info.cipherSuites[0] != scsvRenegotiation { + if info.CipherSuites[0] != scsvRenegotiation { return false } } - if hasGreaseCiphers(info.cipherSuites) { + if hasGreaseCiphers(info.CipherSuites) { return false } @@ -599,19 +615,19 @@ func (info rawHelloInfo) looksLikeSafari() bool { tls.TLS_RSA_WITH_AES_256_CBC_SHA, // 0x35 tls.TLS_RSA_WITH_AES_128_CBC_SHA, // 0x2f } - return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.cipherSuites, true) + return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.CipherSuites, true) } // looksLikeTor returns true if the info looks like a ClientHello from Tor browser // (based on Firefox). func (info rawHelloInfo) looksLikeTor() bool { requiredExtensionsOrder := []uint16{10, 11, 16, 5, 13} - if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) { + if !assertPresenceAndOrdering(requiredExtensionsOrder, info.Extensions, true) { return false } // check for session tickets support; Tor doesn't support them to prevent tracking - for _, ext := range info.extensions { + for _, ext := range info.Extensions { if ext == 35 { return false } @@ -619,12 +635,12 @@ func (info rawHelloInfo) looksLikeTor() bool { // We check for both presence of curves and their ordering, including // an optional curve at the beginning (for Tor based on Firefox 52) - infoCurves := info.curves - if len(info.curves) == 4 { - if info.curves[0] != 29 { + infoCurves := info.Curves + if len(info.Curves) == 4 { + if info.Curves[0] != 29 { return false } - infoCurves = info.curves[1:] + infoCurves = info.Curves[1:] } requiredCurves := []tls.CurveID{23, 24, 25} if len(infoCurves) < len(requiredCurves) { @@ -636,7 +652,7 @@ func (info rawHelloInfo) looksLikeTor() bool { } } - if hasGreaseCiphers(info.cipherSuites) { + if hasGreaseCiphers(info.CipherSuites) { return false } @@ -663,7 +679,7 @@ func (info rawHelloInfo) looksLikeTor() bool { tls.TLS_RSA_WITH_AES_256_CBC_SHA, // 0x35 tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, // 0xa } - return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.cipherSuites, false) + return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.CipherSuites, false) } // assertPresenceAndOrdering will return true if candidateList contains diff --git a/caddyhttp/httpserver/mitm_test.go b/caddyhttp/httpserver/mitm_test.go index e5c2455ad..b83c6bb04 100644 --- a/caddyhttp/httpserver/mitm_test.go +++ b/caddyhttp/httpserver/mitm_test.go @@ -32,44 +32,48 @@ func TestParseClientHello(t *testing.T) { // curl 7.51.0 (x86_64-apple-darwin16.0) libcurl/7.51.0 SecureTransport zlib/1.2.8 inputHex: `010000a6030358a28c73a71bdfc1f09dee13fecdc58805dcce42ac44254df548f14645f7dc2c00004400ffc02cc02bc024c023c00ac009c008c030c02fc028c027c014c013c012009f009e006b0067003900330016009d009c003d003c0035002f000a00af00ae008d008c008b01000039000a00080006001700180019000b00020100000d00120010040102010501060104030203050306030005000501000000000012000000170000`, expected: rawHelloInfo{ - cipherSuites: []uint16{255, 49196, 49195, 49188, 49187, 49162, 49161, 49160, 49200, 49199, 49192, 49191, 49172, 49171, 49170, 159, 158, 107, 103, 57, 51, 22, 157, 156, 61, 60, 53, 47, 10, 175, 174, 141, 140, 139}, - extensions: []uint16{10, 11, 13, 5, 18, 23}, - compressionMethods: []byte{0}, - curves: []tls.CurveID{23, 24, 25}, - points: []uint8{0}, + Version: 0x303, + CipherSuites: []uint16{255, 49196, 49195, 49188, 49187, 49162, 49161, 49160, 49200, 49199, 49192, 49191, 49172, 49171, 49170, 159, 158, 107, 103, 57, 51, 22, 157, 156, 61, 60, 53, 47, 10, 175, 174, 141, 140, 139}, + Extensions: []uint16{10, 11, 13, 5, 18, 23}, + CompressionMethods: []byte{0}, + Curves: []tls.CurveID{23, 24, 25}, + Points: []uint8{0}, }, }, { // Chrome 56 inputHex: `010000c003031dae75222dae1433a5a283ddcde8ddabaefbf16d84f250eee6fdff48cdfff8a00000201a1ac02bc02fc02cc030cca9cca8cc14cc13c013c014009c009d002f0035000a010000777a7a0000ff010001000000000e000c0000096c6f63616c686f73740017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b00020100000a000a0008aaaa001d001700182a2a000100`, expected: rawHelloInfo{ - cipherSuites: []uint16{6682, 49195, 49199, 49196, 49200, 52393, 52392, 52244, 52243, 49171, 49172, 156, 157, 47, 53, 10}, - extensions: []uint16{31354, 65281, 0, 23, 35, 13, 5, 18, 16, 30032, 11, 10, 10794}, - compressionMethods: []byte{0}, - curves: []tls.CurveID{43690, 29, 23, 24}, - points: []uint8{0}, + Version: 0x303, + CipherSuites: []uint16{6682, 49195, 49199, 49196, 49200, 52393, 52392, 52244, 52243, 49171, 49172, 156, 157, 47, 53, 10}, + Extensions: []uint16{31354, 65281, 0, 23, 35, 13, 5, 18, 16, 30032, 11, 10, 10794}, + CompressionMethods: []byte{0}, + Curves: []tls.CurveID{43690, 29, 23, 24}, + Points: []uint8{0}, }, }, { // Firefox 51 inputHex: `010000bd030375f9022fc3a6562467f3540d68013b2d0b961979de6129e944efe0b35531323500001ec02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a010000760000000e000c0000096c6f63616c686f737400170000ff01000100000a000a0008001d001700180019000b00020100002300000010000e000c02683208687474702f312e31000500050100000000ff030000000d0020001e040305030603020308040805080604010501060102010402050206020202`, expected: rawHelloInfo{ - cipherSuites: []uint16{49195, 49199, 52393, 52392, 49196, 49200, 49162, 49161, 49171, 49172, 51, 57, 47, 53, 10}, - extensions: []uint16{0, 23, 65281, 10, 11, 35, 16, 5, 65283, 13}, - compressionMethods: []byte{0}, - curves: []tls.CurveID{29, 23, 24, 25}, - points: []uint8{0}, + Version: 0x303, + CipherSuites: []uint16{49195, 49199, 52393, 52392, 49196, 49200, 49162, 49161, 49171, 49172, 51, 57, 47, 53, 10}, + Extensions: []uint16{0, 23, 65281, 10, 11, 35, 16, 5, 65283, 13}, + CompressionMethods: []byte{0}, + Curves: []tls.CurveID{29, 23, 24, 25}, + Points: []uint8{0}, }, }, { // openssl s_client (OpenSSL 0.9.8zh 14 Jan 2016) inputHex: `0100012b03035d385236b8ca7b7946fa0336f164e76bf821ed90e8de26d97cc677671b6f36380000acc030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f009600410007c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff0201000055000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d0020001e060106020603050105020503040104020403030103020303020102020203000f000101`, expected: rawHelloInfo{ - cipherSuites: []uint16{49200, 49196, 49192, 49188, 49172, 49162, 165, 163, 161, 159, 107, 106, 105, 104, 57, 56, 55, 54, 136, 135, 134, 133, 49202, 49198, 49194, 49190, 49167, 49157, 157, 61, 53, 132, 49199, 49195, 49191, 49187, 49171, 49161, 164, 162, 160, 158, 103, 64, 63, 62, 51, 50, 49, 48, 154, 153, 152, 151, 69, 68, 67, 66, 49201, 49197, 49193, 49189, 49166, 49156, 156, 60, 47, 150, 65, 7, 49169, 49159, 49164, 49154, 5, 4, 49170, 49160, 22, 19, 16, 13, 49165, 49155, 10, 255}, - extensions: []uint16{11, 10, 35, 13, 15}, - compressionMethods: []byte{1, 0}, - curves: []tls.CurveID{23, 25, 28, 27, 24, 26, 22, 14, 13, 11, 12, 9, 10}, - points: []uint8{0, 1, 2}, + Version: 0x303, + CipherSuites: []uint16{49200, 49196, 49192, 49188, 49172, 49162, 165, 163, 161, 159, 107, 106, 105, 104, 57, 56, 55, 54, 136, 135, 134, 133, 49202, 49198, 49194, 49190, 49167, 49157, 157, 61, 53, 132, 49199, 49195, 49191, 49187, 49171, 49161, 164, 162, 160, 158, 103, 64, 63, 62, 51, 50, 49, 48, 154, 153, 152, 151, 69, 68, 67, 66, 49201, 49197, 49193, 49189, 49166, 49156, 156, 60, 47, 150, 65, 7, 49169, 49159, 49164, 49154, 5, 4, 49170, 49160, 22, 19, 16, 13, 49165, 49155, 10, 255}, + Extensions: []uint16{11, 10, 35, 13, 15}, + CompressionMethods: []byte{1, 0}, + Curves: []tls.CurveID{23, 25, 28, 27, 24, 26, 22, 14, 13, 11, 12, 9, 10}, + Points: []uint8{0, 1, 2}, }, }, } { @@ -338,8 +342,8 @@ func TestHeuristicFunctionsAndHandler(t *testing.T) { (isEdge && (isChrome || isFirefox || isSafari || isTor)) || (isTor && (isChrome || isFirefox || isSafari || isEdge)) { t.Errorf("[%s] Test %d: Multiple fingerprinting functions matched: "+ - "Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n\tparsed hello hex: %#x\n", - client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed, parsed) + "Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n", + client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed) } // test the handler and detection results @@ -367,8 +371,8 @@ func TestHeuristicFunctionsAndHandler(t *testing.T) { if got != want { t.Errorf("[%s] Test %d: Expected MITM=%v but got %v (type assertion OK (checked)=%v)", client, i, want, got, checked) - t.Errorf("[%s] Test %d: Looks like Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n\tparsed hello hex: %#x\n", - client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed, parsed) + t.Errorf("[%s] Test %d: Looks like Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n", + client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed) } } } diff --git a/caddyhttp/httpserver/plugin.go b/caddyhttp/httpserver/plugin.go index 16d858b8d..332756f8e 100644 --- a/caddyhttp/httpserver/plugin.go +++ b/caddyhttp/httpserver/plugin.go @@ -30,6 +30,7 @@ import ( "github.com/mholt/caddy/caddyfile" "github.com/mholt/caddy/caddyhttp/staticfiles" "github.com/mholt/caddy/caddytls" + "github.com/mholt/caddy/telemetry" ) const serverType = "http" @@ -66,6 +67,12 @@ func init() { caddy.RegisterParsingCallback(serverType, "root", hideCaddyfile) caddy.RegisterParsingCallback(serverType, "tls", activateHTTPS) caddytls.RegisterConfigGetter(serverType, func(c *caddy.Controller) *caddytls.Config { return GetConfig(c).TLS }) + + // disable the caddytls package reporting ClientHellos + // to telemetry, since our MITM detector does this but + // with more information than the standard lib provides + // (as of May 2018) + caddytls.ClientHelloTelemetry = false } // hideCaddyfile hides the source/origin Caddyfile if it is within the @@ -208,6 +215,18 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd // MakeServers uses the newly-created siteConfigs to // create and return a list of server instances. func (h *httpContext) MakeServers() ([]caddy.Server, error) { + // make a rough estimate as to whether we're in a "production + // environment/system" - start by assuming that most production + // servers will set their default CA endpoint to a public, + // trusted CA (obviously not a perfect hueristic) + var looksLikeProductionCA bool + for _, publicCAEndpoint := range caddytls.KnownACMECAs { + if strings.Contains(caddytls.DefaultCAUrl, publicCAEndpoint) { + looksLikeProductionCA = true + break + } + } + // Iterate each site configuration and make sure that: // 1) TLS is disabled for explicitly-HTTP sites (necessary // when an HTTP address shares a block containing tls) @@ -215,7 +234,22 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) { // currently, QUIC does not support ClientAuth (TODO: // revisit this when our QUIC implementation supports it) // 3) if TLS ClientAuth is used, StrictHostMatching is on + var atLeastOneSiteLooksLikeProduction bool for _, cfg := range h.siteConfigs { + // see if all the addresses (both sites and + // listeners) are loopback to help us determine + // if this is a "production" instance or not + if !atLeastOneSiteLooksLikeProduction { + if !caddy.IsLoopback(cfg.Addr.Host) && + !caddy.IsLoopback(cfg.ListenHost) && + (caddytls.QualifiesForManagedTLS(cfg) || + caddytls.HostQualifies(cfg.Addr.Host)) { + atLeastOneSiteLooksLikeProduction = true + } + } + + // make sure TLS is disabled for explicitly-HTTP sites + // (necessary when HTTP address shares a block containing tls) if !cfg.TLS.Enabled { continue } @@ -265,6 +299,18 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) { servers = append(servers, s) } + // NOTE: This value is only a "good guess". Quite often, development + // environments will use internal DNS or a local hosts file to serve + // real-looking domains in local development. We can't easily tell + // which without doing a DNS lookup, so this guess is definitely naive, + // and if we ever want a better guess, we will have to do DNS lookups. + deploymentGuess := "dev" + if looksLikeProductionCA && atLeastOneSiteLooksLikeProduction { + deploymentGuess = "prod" + } + telemetry.Set("http_deployment_guess", deploymentGuess) + telemetry.Set("http_num_sites", len(h.siteConfigs)) + return servers, nil } diff --git a/caddyhttp/httpserver/server.go b/caddyhttp/httpserver/server.go index c3fbc6691..45f1c639d 100644 --- a/caddyhttp/httpserver/server.go +++ b/caddyhttp/httpserver/server.go @@ -36,6 +36,7 @@ import ( "github.com/mholt/caddy" "github.com/mholt/caddy/caddyhttp/staticfiles" "github.com/mholt/caddy/caddytls" + "github.com/mholt/caddy/telemetry" ) // Server is the HTTP server implementation. @@ -348,6 +349,14 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } }() + // record the User-Agent string (with a cap on its length to mitigate attacks) + ua := r.Header.Get("User-Agent") + if len(ua) > 512 { + ua = ua[:512] + } + go telemetry.AppendUnique("http_user_agent", ua) + go telemetry.Increment("http_request_count") + // copy the original, unchanged URL into the context // so it can be referenced by middlewares urlCopy := *r.URL diff --git a/caddytls/certificates.go b/caddytls/certificates.go index a0d9fb9c4..7df4e11d6 100644 --- a/caddytls/certificates.go +++ b/caddytls/certificates.go @@ -26,6 +26,7 @@ import ( "sync" "time" + "github.com/mholt/caddy/telemetry" "golang.org/x/crypto/ocsp" ) @@ -165,6 +166,7 @@ func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) { if err != nil { return cert, err } + telemetry.Increment("tls_managed_cert_count") return cfg.cacheCertificate(cert), nil } @@ -179,6 +181,7 @@ func (cfg *Config) cacheUnmanagedCertificatePEMFile(certFile, keyFile string) er return err } cfg.cacheCertificate(cert) + telemetry.Increment("tls_manual_cert_count") return nil } @@ -192,6 +195,7 @@ func (cfg *Config) cacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte) return err } cfg.cacheCertificate(cert) + telemetry.Increment("tls_manual_cert_count") return nil } diff --git a/caddytls/client.go b/caddytls/client.go index 7197d917c..2b27f515d 100644 --- a/caddytls/client.go +++ b/caddytls/client.go @@ -26,6 +26,7 @@ import ( "time" "github.com/mholt/caddy" + "github.com/mholt/caddy/telemetry" "github.com/xenolf/lego/acmev2" ) @@ -273,6 +274,8 @@ func (c *ACMEClient) Obtain(name string) error { break } + go telemetry.Increment("tls_acme_certs_obtained") + return nil } @@ -340,6 +343,7 @@ func (c *ACMEClient) Renew(name string) error { } caddy.EmitEvent(caddy.CertRenewEvent, name) + go telemetry.Increment("tls_acme_certs_renewed") return saveCertResource(c.storage, newCertMeta) } @@ -366,6 +370,8 @@ func (c *ACMEClient) Revoke(name string) error { return err } + go telemetry.Increment("tls_acme_certs_revoked") + err = c.storage.DeleteSite(name) if err != nil { return errors.New("certificate revoked, but unable to delete certificate file: " + err.Error()) @@ -417,3 +423,10 @@ func (c *nameCoordinator) Has(name string) bool { c.mu.RUnlock() return ok } + +// KnownACMECAs is a list of ACME directory endpoints of +// known, public, and trusted ACME-compatible certificate +// authorities. +var KnownACMECAs = []string{ + "https://acme-v02.api.letsencrypt.org/directory", +} diff --git a/caddytls/config.go b/caddytls/config.go index fd5b86b5c..9de8332aa 100644 --- a/caddytls/config.go +++ b/caddytls/config.go @@ -23,7 +23,7 @@ import ( "net/url" "strings" - "github.com/codahale/aesnicheck" + "github.com/klauspost/cpuid" "github.com/mholt/caddy" "github.com/xenolf/lego/acmev2" ) @@ -648,7 +648,7 @@ var defaultCiphersNonAESNI = []uint16{ // // See https://github.com/mholt/caddy/issues/1674 func getPreferredDefaultCiphers() []uint16 { - if aesnicheck.HasAESNI() { + if cpuid.CPU.AesNi() { return defaultCiphers } diff --git a/caddytls/config_test.go b/caddytls/config_test.go index 0294e0314..c69f2d550 100644 --- a/caddytls/config_test.go +++ b/caddytls/config_test.go @@ -21,7 +21,7 @@ import ( "reflect" "testing" - "github.com/codahale/aesnicheck" + "github.com/klauspost/cpuid" ) func TestConvertTLSConfigProtocolVersions(t *testing.T) { @@ -98,7 +98,7 @@ func TestConvertTLSConfigCipherSuites(t *testing.T) { func TestGetPreferredDefaultCiphers(t *testing.T) { expectedCiphers := defaultCiphers - if !aesnicheck.HasAESNI() { + if !cpuid.CPU.AesNi() { expectedCiphers = defaultCiphersNonAESNI } diff --git a/caddytls/crypto.go b/caddytls/crypto.go index 51cab7f4d..6ca51bb96 100644 --- a/caddytls/crypto.go +++ b/caddytls/crypto.go @@ -341,7 +341,7 @@ func standaloneTLSTicketKeyRotation(c *tls.Config, ticker *time.Ticker, exitChan // Do not use this for cryptographic purposes. func fastHash(input []byte) string { h := fnv.New32a() - h.Write([]byte(input)) + h.Write(input) return fmt.Sprintf("%x", h.Sum32()) } diff --git a/caddytls/handshake.go b/caddytls/handshake.go index 1e8586d26..8b2639845 100644 --- a/caddytls/handshake.go +++ b/caddytls/handshake.go @@ -25,6 +25,8 @@ import ( "sync" "sync/atomic" "time" + + "github.com/mholt/caddy/telemetry" ) // configGroup is a type that keys configs by their hostname @@ -97,7 +99,27 @@ func (cg configGroup) GetConfigForClient(clientHello *tls.ClientHelloInfo) (*tls // // This method is safe for use as a tls.Config.GetCertificate callback. func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { + if ClientHelloTelemetry && len(clientHello.SupportedVersions) > 0 { + // If no other plugin (such as the HTTP server type) is implementing ClientHello telemetry, we do it. + // NOTE: The values in the Go standard lib's ClientHelloInfo aren't guaranteed to be in order. + info := ClientHelloInfo{ + Version: clientHello.SupportedVersions[0], // report the highest + CipherSuites: clientHello.CipherSuites, + ExtensionsUnknown: true, // no extension info... :( + CompressionMethodsUnknown: true, // no compression methods... :( + Curves: clientHello.SupportedCurves, + Points: clientHello.SupportedPoints, + // We also have, but do not yet use: SignatureSchemes, ServerName, and SupportedProtos (ALPN) + // because the standard lib parses some extensions, but our MITM detector generally doesn't. + } + go telemetry.SetNested("tls_client_hello", info.Key(), info) + } + + // get the certificate and serve it up cert, err := cfg.getCertDuringHandshake(strings.ToLower(clientHello.ServerName), true, true) + if err == nil { + go telemetry.Increment("tls_handshake_count") // TODO: This is a "best guess" for now, we need something listener-level + } return &cert.Certificate, err } @@ -463,6 +485,42 @@ func (cfg *Config) renewDynamicCertificate(name string, currentCert Certificate) return cfg.getCertDuringHandshake(name, true, false) } +// ClientHelloInfo is our own version of the standard lib's +// tls.ClientHelloInfo. As of May 2018, any fields populated +// by the Go standard library are not guaranteed to have their +// values in the original order as on the wire. +type ClientHelloInfo struct { + Version uint16 `json:"version,omitempty"` + CipherSuites []uint16 `json:"cipher_suites,omitempty"` + Extensions []uint16 `json:"extensions,omitempty"` + CompressionMethods []byte `json:"compression,omitempty"` + Curves []tls.CurveID `json:"curves,omitempty"` + Points []uint8 `json:"points,omitempty"` + + // Whether a couple of fields are unknown; if not, the key will encode + // differently to reflect that, as opposed to being known empty values. + // (some fields may be unknown depending on what package is being used; + // i.e. the Go standard lib doesn't expose some things) + // (very important to NOT encode these to JSON) + ExtensionsUnknown bool `json:"-"` + CompressionMethodsUnknown bool `json:"-"` +} + +// Key returns a standardized string form of the data in info, +// useful for identifying duplicates. +func (info ClientHelloInfo) Key() string { + extensions, compressionMethods := "?", "?" + if !info.ExtensionsUnknown { + extensions = fmt.Sprintf("%x", info.Extensions) + } + if !info.CompressionMethodsUnknown { + compressionMethods = fmt.Sprintf("%x", info.CompressionMethods) + } + return fastHash([]byte(fmt.Sprintf("%x-%x-%s-%s-%x-%x", + info.Version, info.CipherSuites, extensions, + compressionMethods, info.Curves, info.Points))) +} + // obtainCertWaitChans is used to coordinate obtaining certs for each hostname. var obtainCertWaitChans = make(map[string]chan struct{}) var obtainCertWaitChansMu sync.Mutex @@ -477,3 +535,8 @@ var failedIssuanceMu sync.RWMutex // If this value is recent, do not make any on-demand certificate requests. var lastIssueTime time.Time var lastIssueTimeMu sync.Mutex + +// ClientHelloTelemetry determines whether to report +// TLS ClientHellos to telemetry. Disable if doing +// it from a different package. +var ClientHelloTelemetry = true diff --git a/caddytls/setup.go b/caddytls/setup.go index 11a5e45de..bcf0cf901 100644 --- a/caddytls/setup.go +++ b/caddytls/setup.go @@ -28,6 +28,7 @@ import ( "strings" "github.com/mholt/caddy" + "github.com/mholt/caddy/telemetry" ) func init() { @@ -174,9 +175,11 @@ func setupTLS(c *caddy.Controller) error { case "max_certs": c.Args(&maxCerts) config.OnDemand = true + telemetry.Increment("tls_on_demand_count") case "ask": c.Args(&askURL) config.OnDemand = true + telemetry.Increment("tls_on_demand_count") case "dns": args := c.RemainingArgs() if len(args) != 1 { @@ -283,6 +286,7 @@ func setupTLS(c *caddy.Controller) error { if err != nil { return fmt.Errorf("self-signed: %v", err) } + telemetry.Increment("tls_self_signed_count") } return nil diff --git a/plugins.go b/plugins.go index b0011009c..5d36f55e2 100644 --- a/plugins.go +++ b/plugins.go @@ -54,32 +54,58 @@ var ( // DescribePlugins returns a string describing the registered plugins. func DescribePlugins() string { + pl := ListPlugins() + str := "Server types:\n" - for name := range serverTypes { + for _, name := range pl["server_types"] { str += " " + name + "\n" } - // List the loaders in registration order str += "\nCaddyfile loaders:\n" + for _, name := range pl["caddyfile_loaders"] { + str += " " + name + "\n" + } + + if len(pl["event_hooks"]) > 0 { + str += "\nEvent hook plugins:\n" + for _, name := range pl["event_hooks"] { + str += " hook." + name + "\n" + } + } + + str += "\nOther plugins:\n" + for _, name := range pl["others"] { + str += " " + name + "\n" + } + + return str +} + +// ListPlugins makes a list of the registered plugins, +// keyed by plugin type. +func ListPlugins() map[string][]string { + p := make(map[string][]string) + + // server type plugins + for name := range serverTypes { + p["server_types"] = append(p["server_types"], name) + } + + // caddyfile loaders in registration order for _, loader := range caddyfileLoaders { - str += " " + loader.name + "\n" + p["caddyfile_loaders"] = append(p["caddyfile_loaders"], loader.name) } if defaultCaddyfileLoader.name != "" { - str += " " + defaultCaddyfileLoader.name + "\n" + p["caddyfile_loaders"] = append(p["caddyfile_loaders"], defaultCaddyfileLoader.name) } // List the event hook plugins - hooks := "" eventHooks.Range(func(k, _ interface{}) bool { - hooks += " hook." + k.(string) + "\n" + p["event_hooks"] = append(p["event_hooks"], k.(string)) return true }) - if hooks != "" { - str += "\nEvent hook plugins:\n" - str += hooks - } - // Let's alphabetize the rest of these... + // alphabetize the rest of the plugins var others []string for stype, stypePlugins := range plugins { for name := range stypePlugins { @@ -93,12 +119,11 @@ func DescribePlugins() string { } sort.Strings(others) - str += "\nOther plugins:\n" for _, name := range others { - str += " " + name + "\n" + p["others"] = append(p["others"], name) } - return str + return p } // ValidDirectives returns the list of all directives that are diff --git a/sigtrap.go b/sigtrap.go index ac61c59c0..dbb01b7ec 100644 --- a/sigtrap.go +++ b/sigtrap.go @@ -19,6 +19,8 @@ import ( "os" "os/signal" "sync" + + "github.com/mholt/caddy/telemetry" ) // TrapSignals create signal handlers for all applicable signals for this @@ -52,6 +54,9 @@ func trapSignalsCrossPlatform() { log.Println("[INFO] SIGINT: Shutting down") + telemetry.AppendUnique("sigtrap", "SIGINT") + go telemetry.StopEmitting() // not guaranteed to finish in time; that's OK (just don't block!) + // important cleanup actions before shutdown callbacks for _, f := range OnProcessExit { f() diff --git a/sigtrap_posix.go b/sigtrap_posix.go index a14f9d356..16c8e8616 100644 --- a/sigtrap_posix.go +++ b/sigtrap_posix.go @@ -21,6 +21,8 @@ import ( "os" "os/signal" "syscall" + + "github.com/mholt/caddy/telemetry" ) // trapSignalsPosix captures POSIX-only signals. @@ -49,10 +51,15 @@ func trapSignalsPosix() { log.Printf("[ERROR] SIGTERM stop: %v", err) exitCode = 3 } + + telemetry.AppendUnique("sigtrap", "SIGTERM") + go telemetry.StopEmitting() // won't finish in time, but that's OK - just don't block + os.Exit(exitCode) case syscall.SIGUSR1: log.Println("[INFO] SIGUSR1: Reloading") + go telemetry.AppendUnique("sigtrap", "SIGUSR1") // Start with the existing Caddyfile caddyfileToUse, inst, err := getCurrentCaddyfile() @@ -92,12 +99,14 @@ func trapSignalsPosix() { case syscall.SIGUSR2: log.Println("[INFO] SIGUSR2: Upgrading") + go telemetry.AppendUnique("sigtrap", "SIGUSR2") if err := Upgrade(); err != nil { log.Printf("[ERROR] SIGUSR2: upgrading: %v", err) } case syscall.SIGHUP: // ignore; this signal is sometimes sent outside of the user's control + go telemetry.AppendUnique("sigtrap", "SIGHUP") } } }() diff --git a/telemetry/collection.go b/telemetry/collection.go new file mode 100644 index 000000000..a46e2caf1 --- /dev/null +++ b/telemetry/collection.go @@ -0,0 +1,296 @@ +// Copyright 2015 Light Code Labs, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package telemetry + +import ( + "log" + "strings" + + "github.com/google/uuid" +) + +// Init initializes this package so that it may +// be used. Do not call this function more than +// once. Init panics if it is called more than +// once or if the UUID value is empty. Once this +// function is called, the rest of the package +// may safely be used. If this function is not +// called, the collector functions may still be +// invoked, but they will be no-ops. +// +// Any metrics keys that are passed in the second +// argument will be permanently disabled for the +// lifetime of the process. +func Init(instanceID uuid.UUID, disabledMetricsKeys []string) { + if enabled { + panic("already initialized") + } + if str := instanceID.String(); str == "" || + str == "00000000-0000-0000-0000-000000000000" { + panic("empty UUID") + } + instanceUUID = instanceID + disabledMetricsMu.Lock() + for _, key := range disabledMetricsKeys { + disabledMetrics[strings.TrimSpace(key)] = false + } + disabledMetricsMu.Unlock() + enabled = true +} + +// StartEmitting sends the current payload and begins the +// transmission cycle for updates. This is the first +// update sent, and future ones will be sent until +// StopEmitting is called. +// +// This function is non-blocking (it spawns a new goroutine). +// +// This function panics if it was called more than once. +// It is a no-op if this package was not initialized. +func StartEmitting() { + if !enabled { + return + } + updateTimerMu.Lock() + if updateTimer != nil { + updateTimerMu.Unlock() + panic("updates already started") + } + updateTimerMu.Unlock() + updateMu.Lock() + if updating { + updateMu.Unlock() + panic("update already in progress") + } + updateMu.Unlock() + go logEmit(false) +} + +// StopEmitting sends the current payload and terminates +// the update cycle. No more updates will be sent. +// +// It is a no-op if the package was never initialized +// or if emitting was never started. +// +// NOTE: This function is blocking. Run in a goroutine if +// you want to guarantee no blocking at critical times +// like exiting the program. +func StopEmitting() { + if !enabled { + return + } + updateTimerMu.Lock() + if updateTimer == nil { + updateTimerMu.Unlock() + return + } + updateTimerMu.Unlock() + logEmit(true) // likely too early; may take minutes to return +} + +// Reset empties the current payload buffer. +func Reset() { + resetBuffer() +} + +// Set puts a value in the buffer to be included +// in the next emission. It overwrites any +// previous value. +// +// This function is safe for multiple goroutines, +// and it is recommended to call this using the +// go keyword after the call to SendHello so it +// doesn't block crucial code. +func Set(key string, val interface{}) { + if !enabled || isDisabled(key) { + return + } + bufferMu.Lock() + if _, ok := buffer[key]; !ok { + if bufferItemCount >= maxBufferItems { + bufferMu.Unlock() + return + } + bufferItemCount++ + } + buffer[key] = val + bufferMu.Unlock() +} + +// SetNested puts a value in the buffer to be included +// in the next emission, nested under the top-level key +// as subkey. It overwrites any previous value. +// +// This function is safe for multiple goroutines, +// and it is recommended to call this using the +// go keyword after the call to SendHello so it +// doesn't block crucial code. +func SetNested(key, subkey string, val interface{}) { + if !enabled || isDisabled(key) { + return + } + bufferMu.Lock() + if topLevel, ok1 := buffer[key]; ok1 { + topLevelMap, ok2 := topLevel.(map[string]interface{}) + if !ok2 { + bufferMu.Unlock() + log.Printf("[PANIC] Telemetry: key %s is already used for non-nested-map value", key) + return + } + if _, ok3 := topLevelMap[subkey]; !ok3 { + // don't exceed max buffer size + if bufferItemCount >= maxBufferItems { + bufferMu.Unlock() + return + } + bufferItemCount++ + } + topLevelMap[subkey] = val + } else { + // don't exceed max buffer size + if bufferItemCount >= maxBufferItems { + bufferMu.Unlock() + return + } + bufferItemCount++ + buffer[key] = map[string]interface{}{subkey: val} + } + bufferMu.Unlock() +} + +// Append appends value to a list named key. +// If key is new, a new list will be created. +// If key maps to a type that is not a list, +// a panic is logged, and this is a no-op. +func Append(key string, value interface{}) { + if !enabled || isDisabled(key) { + return + } + bufferMu.Lock() + if bufferItemCount >= maxBufferItems { + bufferMu.Unlock() + return + } + // TODO: Test this... + bufVal, inBuffer := buffer[key] + sliceVal, sliceOk := bufVal.([]interface{}) + if inBuffer && !sliceOk { + bufferMu.Unlock() + log.Printf("[PANIC] Telemetry: key %s already used for non-slice value", key) + return + } + if sliceVal == nil { + buffer[key] = []interface{}{value} + } else if sliceOk { + buffer[key] = append(sliceVal, value) + } + bufferItemCount++ + bufferMu.Unlock() +} + +// AppendUnique adds value to a set named key. +// Set items are unordered. Values in the set +// are unique, but how many times they are +// appended is counted. The value must be +// hashable. +// +// If key is new, a new set will be created for +// values with that key. If key maps to a type +// that is not a counting set, a panic is logged, +// and this is a no-op. +func AppendUnique(key string, value interface{}) { + if !enabled || isDisabled(key) { + return + } + bufferMu.Lock() + bufVal, inBuffer := buffer[key] + setVal, setOk := bufVal.(countingSet) + if inBuffer && !setOk { + bufferMu.Unlock() + log.Printf("[PANIC] Telemetry: key %s already used for non-counting-set value", key) + return + } + if setVal == nil { + // ensure the buffer is not too full, then add new unique value + if bufferItemCount >= maxBufferItems { + bufferMu.Unlock() + return + } + buffer[key] = countingSet{value: 1} + bufferItemCount++ + } else if setOk { + // unique value already exists, so just increment counter + setVal[value]++ + } + bufferMu.Unlock() +} + +// Add adds amount to a value named key. +// If it does not exist, it is created with +// a value of 1. If key maps to a type that +// is not an integer, a panic is logged, +// and this is a no-op. +func Add(key string, amount int) { + atomicAdd(key, amount) +} + +// Increment is a shortcut for Add(key, 1) +func Increment(key string) { + atomicAdd(key, 1) +} + +// atomicAdd adds amount (negative to subtract) +// to key. +func atomicAdd(key string, amount int) { + if !enabled || isDisabled(key) { + return + } + bufferMu.Lock() + bufVal, inBuffer := buffer[key] + intVal, intOk := bufVal.(int) + if inBuffer && !intOk { + bufferMu.Unlock() + log.Printf("[PANIC] Telemetry: key %s already used for non-integer value", key) + return + } + if !inBuffer { + if bufferItemCount >= maxBufferItems { + bufferMu.Unlock() + return + } + bufferItemCount++ + } + buffer[key] = intVal + amount + bufferMu.Unlock() +} + +// isDisabled returns whether key is +// a disabled metric key. ALL collection +// functions should call this and not +// save the value if this returns true. +func isDisabled(key string) bool { + // for keys that are augmented with data, such as + // "tls_client_hello_ua:", just + // check the prefix "tls_client_hello_ua" + checkKey := key + if idx := strings.Index(key, ":"); idx > -1 { + checkKey = key[:idx] + } + + disabledMetricsMu.RLock() + _, ok := disabledMetrics[checkKey] + disabledMetricsMu.RUnlock() + return ok +} diff --git a/telemetry/collection_test.go b/telemetry/collection_test.go new file mode 100644 index 000000000..4199b684a --- /dev/null +++ b/telemetry/collection_test.go @@ -0,0 +1,109 @@ +// Copyright 2015 Light Code Labs, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package telemetry + +import ( + "fmt" + "testing" + + "github.com/google/uuid" +) + +func TestInit(t *testing.T) { + reset() + + id := doInit(t) // should not panic + + defer func() { + if r := recover(); r == nil { + t.Errorf("Second call to Init should have panicked") + } + }() + Init(id, nil) // should panic +} + +func TestInitEmptyUUID(t *testing.T) { + reset() + defer func() { + if r := recover(); r == nil { + t.Errorf("Call to Init with empty UUID should have panicked") + } + }() + Init(uuid.UUID([16]byte{}), nil) +} + +func TestSet(t *testing.T) { + reset() + + // should be no-op since we haven't called Init() yet + Set("test1", "foobar") + if _, ok := buffer["test"]; ok { + t.Errorf("Should not have inserted item when not initialized") + } + + // should work after we've initialized + doInit(t) + Set("test1", "foobar") + val, ok := buffer["test1"] + if !ok { + t.Errorf("Expected value to be in buffer, but it wasn't") + } else if val.(string) != "foobar" { + t.Errorf("Expected 'foobar', got '%v'", val) + } + + // should not overfill buffer + maxBufferItemsTmp := maxBufferItems + maxBufferItems = 10 + for i := 0; i < maxBufferItems+1; i++ { + Set(fmt.Sprintf("overfill_%d", i), "foobar") + } + if len(buffer) > maxBufferItems { + t.Errorf("Should not exceed max buffer size (%d); has %d items", + maxBufferItems, len(buffer)) + } + maxBufferItems = maxBufferItemsTmp + + // Should overwrite values + Set("test1", "foobar2") + val, ok = buffer["test1"] + if !ok { + t.Errorf("Expected value to be in buffer, but it wasn't") + } else if val.(string) != "foobar2" { + t.Errorf("Expected 'foobar2', got '%v'", val) + } +} + +// doInit calls Init() with a valid UUID +// and returns it. +func doInit(t *testing.T) uuid.UUID { + id, err := uuid.Parse(testUUID) + if err != nil { + t.Fatalf("Could not make UUID: %v", err) + } + Init(id, nil) + return id +} + +// reset resets all the lovely package-level state; +// can be used as a set up function in tests. +func reset() { + instanceUUID = uuid.UUID{} + buffer = make(map[string]interface{}) + bufferItemCount = 0 + updating = false + enabled = false +} + +const testUUID = "0b6cfa22-0d4c-11e8-b11b-7a0058e13201" diff --git a/telemetry/telemetry.go b/telemetry/telemetry.go new file mode 100644 index 000000000..183ee6ea7 --- /dev/null +++ b/telemetry/telemetry.go @@ -0,0 +1,413 @@ +// Copyright 2015 Light Code Labs, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package telemetry implements the client for server-side telemetry +// of the network. Functions in this package are synchronous and blocking +// unless otherwise specified. For convenience, most functions here do +// not return errors, but errors are logged to the standard logger. +// +// To use this package, first call Init(). You can then call any of the +// collection/aggregation functions. Call StartEmitting() when you are +// ready to begin sending telemetry updates. +// +// When collecting metrics (functions like Set, AppendUnique, or Increment), +// it may be desirable and even recommended to invoke them in a new +// goroutine in case there is lock contention; they are thread-safe (unless +// noted), and you may not want them to block the main thread of execution. +// However, sometimes blocking may be necessary too; for example, adding +// startup metrics to the buffer before the call to StartEmitting(). +// +// This package is designed to be as fast and space-efficient as reasonably +// possible, so that it does not disrupt the flow of execution. +package telemetry + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "math/rand" + "net/http" + "strconv" + "strings" + "sync" + "time" + + "github.com/google/uuid" +) + +// logEmit calls emit and then logs the error, if any. +// See docs for emit. +func logEmit(final bool) { + err := emit(final) + if err != nil { + log.Printf("[ERROR] Sending telemetry: %v", err) + } +} + +// emit sends an update to the telemetry server. +// Set final to true if this is the last call to emit. +// If final is true, no future updates will be scheduled. +// Otherwise, the next update will be scheduled. +func emit(final bool) error { + if !enabled { + return fmt.Errorf("telemetry not enabled") + } + + // ensure only one update happens at a time; + // skip update if previous one still in progress + updateMu.Lock() + if updating { + updateMu.Unlock() + log.Println("[NOTICE] Skipping this telemetry update because previous one is still working") + return nil + } + updating = true + updateMu.Unlock() + defer func() { + updateMu.Lock() + updating = false + updateMu.Unlock() + }() + + // terminate any pending update if this is the last one + if final { + stopUpdateTimer() + } + + payloadBytes, err := makePayloadAndResetBuffer() + if err != nil { + return err + } + + // this will hold the server's reply + var reply Response + + // transmit the payload - use a loop to retry in case of failure + for i := 0; i < 4; i++ { + if i > 0 && err != nil { + // don't hammer the server; first failure might have been + // a fluke, but back off more after that + log.Printf("[WARNING] Sending telemetry (attempt %d): %v - backing off and retrying", i, err) + time.Sleep(time.Duration((i+1)*(i+1)*(i+1)) * time.Second) + } + + // send it + var resp *http.Response + resp, err = httpClient.Post(endpoint+instanceUUID.String(), "application/json", bytes.NewReader(payloadBytes)) + if err != nil { + continue + } + + // check for any special-case response codes + if resp.StatusCode == http.StatusGone { + // the endpoint has been deprecated and is no longer servicing clients + err = fmt.Errorf("telemetry server replied with HTTP %d; upgrade required", resp.StatusCode) + if clen := resp.Header.Get("Content-Length"); clen != "0" && clen != "" { + bodyBytes, readErr := ioutil.ReadAll(resp.Body) + if readErr != nil { + log.Printf("[ERROR] Reading response body from server: %v", readErr) + } + err = fmt.Errorf("%v - %s", err, bodyBytes) + } + resp.Body.Close() + reply.Stop = true + break + } + if resp.StatusCode == http.StatusUnavailableForLegalReasons { + // the endpoint is unavailable, at least to this client, for legal reasons (!) + err = fmt.Errorf("telemetry server replied with HTTP %d %s: please consult the project website and developers for guidance", resp.StatusCode, resp.Status) + if clen := resp.Header.Get("Content-Length"); clen != "0" && clen != "" { + bodyBytes, readErr := ioutil.ReadAll(resp.Body) + if readErr != nil { + log.Printf("[ERROR] Reading response body from server: %v", readErr) + } + err = fmt.Errorf("%v - %s", err, bodyBytes) + } + resp.Body.Close() + reply.Stop = true + break + } + + // okay, ensure we can interpret the response + if ct := resp.Header.Get("Content-Type"); (resp.StatusCode < 300 || resp.StatusCode >= 400) && + !strings.Contains(ct, "json") { + err = fmt.Errorf("telemetry server replied with unknown content-type: '%s' and HTTP %s", ct, resp.Status) + resp.Body.Close() + continue + } + + // read the response body + err = json.NewDecoder(resp.Body).Decode(&reply) + resp.Body.Close() // close response body as soon as we're done with it + if err != nil { + continue + } + + // update the list of enabled/disabled keys, if any + for _, key := range reply.EnableKeys { + disabledMetricsMu.Lock() + // only re-enable this metric if it is temporarily disabled + if temp, ok := disabledMetrics[key]; ok && temp { + delete(disabledMetrics, key) + } + disabledMetricsMu.Unlock() + } + for _, key := range reply.DisableKeys { + disabledMetricsMu.Lock() + disabledMetrics[key] = true // all remotely-disabled keys are "temporarily" disabled + disabledMetricsMu.Unlock() + } + + // make sure we didn't send the update too soon; if so, + // just wait and try again -- this is a special case of + // error that we handle differently, as you can see + if resp.StatusCode == http.StatusTooManyRequests { + if reply.NextUpdate <= 0 { + raStr := resp.Header.Get("Retry-After") + if ra, err := strconv.Atoi(raStr); err == nil { + reply.NextUpdate = time.Duration(ra) * time.Second + } + } + if !final { + log.Printf("[NOTICE] Sending telemetry: we were too early; waiting %s before trying again", reply.NextUpdate) + time.Sleep(reply.NextUpdate) + continue + } + } else if resp.StatusCode >= 400 { + err = fmt.Errorf("telemetry server returned status code %d", resp.StatusCode) + continue + } + + break + } + if err == nil && !final { + // (remember, if there was an error, we return it + // below, so it WILL get logged if it's supposed to) + log.Println("[INFO] Sending telemetry: success") + } + + // even if there was an error after all retries, we should + // schedule the next update using our default update + // interval because the server might be healthy later + + // ensure we won't slam the telemetry server; add a little variance + if reply.NextUpdate < 1*time.Second { + reply.NextUpdate = defaultUpdateInterval + time.Duration(rand.Intn(int(1*time.Minute))) + } + + // schedule the next update (if this wasn't the last one and + // if the remote server didn't tell us to stop sending) + if !final && !reply.Stop { + updateTimerMu.Lock() + updateTimer = time.AfterFunc(reply.NextUpdate, func() { + logEmit(false) + }) + updateTimerMu.Unlock() + } + + return err +} + +func stopUpdateTimer() { + updateTimerMu.Lock() + updateTimer.Stop() + updateTimer = nil + updateTimerMu.Unlock() +} + +// makePayloadAndResetBuffer prepares a payload +// by emptying the collection buffer. It returns +// the bytes of the payload to send to the server. +// Since the buffer is reset by this, if the +// resulting byte slice is lost, the payload is +// gone with it. +func makePayloadAndResetBuffer() ([]byte, error) { + bufCopy := resetBuffer() + + // encode payload in preparation for transmission + payload := Payload{ + InstanceID: instanceUUID.String(), + Timestamp: time.Now().UTC(), + Data: bufCopy, + } + return json.Marshal(payload) +} + +// resetBuffer makes a local pointer to the buffer, +// then resets the buffer by assigning to be a newly- +// made value to clear it out, then sets the buffer +// item count to 0. It returns the copied pointer to +// the original map so the old buffer value can be +// used locally. +func resetBuffer() map[string]interface{} { + bufferMu.Lock() + bufCopy := buffer + buffer = make(map[string]interface{}) + bufferItemCount = 0 + bufferMu.Unlock() + return bufCopy +} + +// Response contains the body of a response from the +// telemetry server. +type Response struct { + // NextUpdate is how long to wait before the next update. + NextUpdate time.Duration `json:"next_update"` + + // Stop instructs the telemetry server to stop sending + // telemetry. This would only be done under extenuating + // circumstances, but we are prepared for it nonetheless. + Stop bool `json:"stop,omitempty"` + + // Error will be populated with an error message, if any. + // This field should be empty if the status code is < 400. + Error string `json:"error,omitempty"` + + // DisableKeys will contain a list of keys/metrics that + // should NOT be sent until further notice. The client + // must NOT store these items in its buffer or send them + // to the telemetry server while they are disabled. If + // this list and EnableKeys have the same value (which is + // not supposed to happen), this field should dominate. + DisableKeys []string `json:"disable_keys,omitempty"` + + // EnableKeys will contain a list of keys/metrics that + // MAY be sent until further notice. + EnableKeys []string `json:"enable_keys,omitempty"` +} + +// Payload is the data that gets sent to the telemetry server. +type Payload struct { + // The universally unique ID of the instance + InstanceID string `json:"instance_id"` + + // The UTC timestamp of the transmission + Timestamp time.Time `json:"timestamp"` + + // The timestamp before which the next update is expected + // (NOT populated by client - the server fills this in + // before it stores the data) + ExpectNext time.Time `json:"expect_next,omitempty"` + + // The metrics + Data map[string]interface{} `json:"data,omitempty"` +} + +// Int returns the value of the data keyed by key +// if it is an integer; otherwise it returns 0. +func (p Payload) Int(key string) int { + val, _ := p.Data[key] + switch p.Data[key].(type) { + case int: + return val.(int) + case float64: // after JSON-decoding, int becomes float64... + return int(val.(float64)) + } + return 0 +} + +// countingSet implements a set that counts how many +// times a key is inserted. It marshals to JSON in a +// way such that keys are converted to values next +// to their associated counts. +type countingSet map[interface{}]int + +// MarshalJSON implements the json.Marshaler interface. +// It converts the set to an array so that the values +// are JSON object values instead of keys, since keys +// are difficult to query in databases. +func (s countingSet) MarshalJSON() ([]byte, error) { + type Item struct { + Value interface{} `json:"value"` + Count int `json:"count"` + } + var list []Item + + for k, v := range s { + list = append(list, Item{Value: k, Count: v}) + } + + return json.Marshal(list) +} + +var ( + // httpClient should be used for HTTP requests. It + // is configured with a timeout for reliability. + httpClient = http.Client{ + Transport: &http.Transport{ + TLSHandshakeTimeout: 30 * time.Second, + DisableKeepAlives: true, + }, + Timeout: 1 * time.Minute, + } + + // buffer holds the data that we are building up to send. + buffer = make(map[string]interface{}) + bufferItemCount = 0 + bufferMu sync.RWMutex // protects both the buffer and its count + + // updating is used to ensure only one + // update happens at a time. + updating bool + updateMu sync.Mutex + + // updateTimer fires off the next update. + // If no update is scheduled, this is nil. + updateTimer *time.Timer + updateTimerMu sync.Mutex + + // disabledMetrics is a set of metric keys + // that should NOT be saved to the buffer + // or sent to the telemetry server. The value + // indicates whether the entry is temporary. + // If the value is true, it may be removed if + // the metric is re-enabled remotely later. If + // the value is false, it is permanent + // (presumably becaues the user explicitly + // disabled it) and can only be re-enabled + // with user consent. + disabledMetrics = make(map[string]bool) + disabledMetricsMu sync.RWMutex + + // instanceUUID is the ID of the current instance. + // This MUST be set to emit telemetry. + // This MUST NOT be openly exposed to clients, for privacy. + instanceUUID uuid.UUID + + // enabled indicates whether the package has + // been initialized and can be actively used. + enabled bool + + // maxBufferItems is the maximum number of items we'll allow + // in the buffer before we start dropping new ones, in a + // rough (simple) attempt to keep memory use under control. + maxBufferItems = 100000 +) + +const ( + // endpoint is the base URL to remote telemetry server; + // the instance ID will be appended to it. + endpoint = "https://telemetry-staging.caddyserver.com/v1/update/" + + // defaultUpdateInterval is how long to wait before emitting + // more telemetry data if all retires fail. This value is + // only used if the client receives a nonsensical value, or + // doesn't send one at all, or if a connection can't be made, + // likely indicating a problem with the server. Thus, this + // value should be a long duration to help alleviate extra + // load on the server. + defaultUpdateInterval = 1 * time.Hour +) diff --git a/telemetry/telemetry_test.go b/telemetry/telemetry_test.go new file mode 100644 index 000000000..9ff4ad618 --- /dev/null +++ b/telemetry/telemetry_test.go @@ -0,0 +1,59 @@ +// Copyright 2015 Light Code Labs, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package telemetry + +import ( + "encoding/json" + "testing" +) + +func TestMakePayloadAndResetBuffer(t *testing.T) { + reset() + id := doInit(t) + + buffer = map[string]interface{}{ + "foo1": "bar1", + "foo2": "bar2", + } + bufferItemCount = 2 + + payloadBytes, err := makePayloadAndResetBuffer() + if err != nil { + t.Fatalf("Error making payload bytes: %v", err) + } + + if len(buffer) != 0 { + t.Errorf("Expected buffer len to be 0, got %d", len(buffer)) + } + if bufferItemCount != 0 { + t.Errorf("Expected buffer item count to be 0, got %d", bufferItemCount) + } + + var payload Payload + err = json.Unmarshal(payloadBytes, &payload) + if err != nil { + t.Fatalf("Error deserializing payload: %v", err) + } + + if payload.InstanceID != id.String() { + t.Errorf("Expected instance ID to be set to '%s' but got '%s'", testUUID, payload.InstanceID) + } + if payload.Data == nil { + t.Errorf("Expected data to be set, but was nil") + } + if payload.Timestamp.IsZero() { + t.Errorf("Expected timestamp to be set, but was zero value") + } +} diff --git a/vendor/github.com/codahale/aesnicheck/asm_amd64.s b/vendor/github.com/codahale/aesnicheck/asm_amd64.s deleted file mode 100644 index d7ee7ee38..000000000 --- a/vendor/github.com/codahale/aesnicheck/asm_amd64.s +++ /dev/null @@ -1,9 +0,0 @@ -// func HasAESNI() bool -TEXT ·HasAESNI(SB),$0 - XORQ AX, AX - INCL AX - CPUID - SHRQ $25, CX - ANDQ $1, CX - MOVB CX, ret+0(FP) - RET diff --git a/vendor/github.com/codahale/aesnicheck/check_asm.go b/vendor/github.com/codahale/aesnicheck/check_asm.go deleted file mode 100644 index 7b4d332cd..000000000 --- a/vendor/github.com/codahale/aesnicheck/check_asm.go +++ /dev/null @@ -1,6 +0,0 @@ -// +build amd64 - -package aesnicheck - -// HasAESNI returns whether AES-NI is supported by the CPU. -func HasAESNI() bool diff --git a/vendor/github.com/codahale/aesnicheck/check_generic.go b/vendor/github.com/codahale/aesnicheck/check_generic.go deleted file mode 100644 index b80816038..000000000 --- a/vendor/github.com/codahale/aesnicheck/check_generic.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build !amd64 - -package aesnicheck - -// HasAESNI returns whether AES-NI is supported by the CPU. -func HasAESNI() bool { - return false -} diff --git a/vendor/github.com/codahale/aesnicheck/cmd/aesnicheck/aesnicheck.go b/vendor/github.com/codahale/aesnicheck/cmd/aesnicheck/aesnicheck.go deleted file mode 100644 index ecfe1ce81..000000000 --- a/vendor/github.com/codahale/aesnicheck/cmd/aesnicheck/aesnicheck.go +++ /dev/null @@ -1,22 +0,0 @@ -// Command aesnicheck queries the CPU for AES-NI support. If AES-NI is supported, -// aesnicheck will print "supported" and exit with a status of 0. If AES-NI is -// not supported, aesnicheck will print "unsupported" and exit with a status of -// -1. -package main - -import ( - "fmt" - "os" - - "github.com/codahale/aesnicheck" -) - -func main() { - if aesnicheck.HasAESNI() { - fmt.Println("supported") - os.Exit(0) - } else { - fmt.Println("unsupported") - os.Exit(-1) - } -} diff --git a/vendor/github.com/codahale/aesnicheck/docs.go b/vendor/github.com/codahale/aesnicheck/docs.go deleted file mode 100644 index 54fa03e61..000000000 --- a/vendor/github.com/codahale/aesnicheck/docs.go +++ /dev/null @@ -1,9 +0,0 @@ -// Package aesnicheck provides a simple check to see if crypto/aes is using -// AES-NI instructions or if the AES transform is being done in software. AES-NI -// is constant-time, which makes it impervious to cache-level timing attacks. For -// security-conscious deployments on public cloud infrastructure (Amazon EC2, -// Google Compute Engine, Microsoft Azure, etc.) this may be critical. -// -// See http://eprint.iacr.org/2014/248 for details on cross-VM timing attacks on -// AES keys. -package aesnicheck diff --git a/vendor/github.com/google/uuid/hash.go b/vendor/github.com/google/uuid/hash.go index 4fc5a77df..b17461631 100644 --- a/vendor/github.com/google/uuid/hash.go +++ b/vendor/github.com/google/uuid/hash.go @@ -27,7 +27,7 @@ var ( func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { h.Reset() h.Write(space[:]) - h.Write([]byte(data)) + h.Write(data) s := h.Sum(nil) var uuid UUID copy(uuid[:], s) diff --git a/vendor/github.com/google/uuid/node.go b/vendor/github.com/google/uuid/node.go index 5f0156a2e..384f07d02 100644 --- a/vendor/github.com/google/uuid/node.go +++ b/vendor/github.com/google/uuid/node.go @@ -5,16 +5,14 @@ package uuid import ( - "net" "sync" ) var ( - nodeMu sync.Mutex - interfaces []net.Interface // cached list of interfaces - ifname string // name of interface being used - nodeID [6]byte // hardware for version 1 UUIDs - zeroID [6]byte // nodeID with only 0's + nodeMu sync.Mutex + ifname string // name of interface being used + nodeID [6]byte // hardware for version 1 UUIDs + zeroID [6]byte // nodeID with only 0's ) // NodeInterface returns the name of the interface from which the NodeID was @@ -39,20 +37,12 @@ func SetNodeInterface(name string) bool { } func setNodeInterface(name string) bool { - if interfaces == nil { - var err error - interfaces, err = net.Interfaces() - if err != nil && name != "" { - return false - } - } - for _, ifs := range interfaces { - if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { - copy(nodeID[:], ifs.HardwareAddr) - ifname = ifs.Name - return true - } + iname, addr := getHardwareInterface(name) // null implementation for js + if iname != "" && addr != nil { + ifname = iname + copy(nodeID[:], addr) + return true } // We found no interfaces with a valid hardware address. If name @@ -94,9 +84,6 @@ func SetNodeID(id []byte) bool { // NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is // not valid. The NodeID is only well defined for version 1 and 2 UUIDs. func (uuid UUID) NodeID() []byte { - if len(uuid) != 16 { - return nil - } var node [6]byte copy(node[:], uuid[10:]) return node[:] diff --git a/vendor/github.com/google/uuid/node_js.go b/vendor/github.com/google/uuid/node_js.go new file mode 100644 index 000000000..24b78edc9 --- /dev/null +++ b/vendor/github.com/google/uuid/node_js.go @@ -0,0 +1,12 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build js + +package uuid + +// getHardwareInterface returns nil values for the JS version of the code. +// This remvoves the "net" dependency, because it is not used in the browser. +// Using the "net" library inflates the size of the transpiled JS code by 673k bytes. +func getHardwareInterface(name string) (string, []byte) { return "", nil } diff --git a/vendor/github.com/google/uuid/node_net.go b/vendor/github.com/google/uuid/node_net.go new file mode 100644 index 000000000..0cbbcddbd --- /dev/null +++ b/vendor/github.com/google/uuid/node_net.go @@ -0,0 +1,33 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !js + +package uuid + +import "net" + +var interfaces []net.Interface // cached list of interfaces + +// getHardwareInterface returns the name and hardware address of interface name. +// If name is "" then the name and hardware address of one of the system's +// interfaces is returned. If no interfaces are found (name does not exist or +// there are no interfaces) then "", nil is returned. +// +// Only addresses of at least 6 bytes are returned. +func getHardwareInterface(name string) (string, []byte) { + if interfaces == nil { + var err error + interfaces, err = net.Interfaces() + if err != nil { + return "", nil + } + } + for _, ifs := range interfaces { + if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { + return ifs.Name, ifs.HardwareAddr + } + } + return "", nil +} diff --git a/vendor/github.com/google/uuid/time.go b/vendor/github.com/google/uuid/time.go index fd7fe0ac4..e6ef06cdc 100644 --- a/vendor/github.com/google/uuid/time.go +++ b/vendor/github.com/google/uuid/time.go @@ -86,7 +86,7 @@ func clockSequence() int { return int(clockSeq & 0x3fff) } -// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to +// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to // -1 causes a new sequence to be generated. func SetClockSequence(seq int) { defer timeMu.Unlock() @@ -100,9 +100,9 @@ func setClockSequence(seq int) { randomBits(b[:]) // clock sequence seq = int(b[0])<<8 | int(b[1]) } - old_seq := clockSeq + oldSeq := clockSeq clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant - if old_seq != clockSeq { + if oldSeq != clockSeq { lasttime = 0 } } diff --git a/vendor/github.com/google/uuid/uuid.go b/vendor/github.com/google/uuid/uuid.go index 23161a86c..7f3643fe9 100644 --- a/vendor/github.com/google/uuid/uuid.go +++ b/vendor/github.com/google/uuid/uuid.go @@ -58,11 +58,11 @@ func Parse(s string) (UUID, error) { 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} { - if v, ok := xtob(s[x], s[x+1]); !ok { + v, ok := xtob(s[x], s[x+1]) + if !ok { return uuid, errors.New("invalid UUID format") - } else { - uuid[i] = v } + uuid[i] = v } return uuid, nil } @@ -88,15 +88,22 @@ func ParseBytes(b []byte) (UUID, error) { 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} { - if v, ok := xtob(b[x], b[x+1]); !ok { + v, ok := xtob(b[x], b[x+1]) + if !ok { return uuid, errors.New("invalid UUID format") - } else { - uuid[i] = v } + uuid[i] = v } return uuid, nil } +// FromBytes creates a new UUID from a byte slice. Returns an error if the slice +// does not have a length of 16. The bytes are copied from the slice. +func FromBytes(b []byte) (uuid UUID, err error) { + err = uuid.UnmarshalBinary(b) + return uuid, err +} + // Must returns uuid if err is nil and panics otherwise. func Must(uuid UUID, err error) UUID { if err != nil { diff --git a/vendor/github.com/google/uuid/version4.go b/vendor/github.com/google/uuid/version4.go index 74c4e6c9f..84af91c9f 100644 --- a/vendor/github.com/google/uuid/version4.go +++ b/vendor/github.com/google/uuid/version4.go @@ -14,7 +14,7 @@ func New() UUID { return Must(NewRandom()) } -// NewRandom returns a Random (Version 4) UUID or panics. +// NewRandom returns a Random (Version 4) UUID. // // The strength of the UUIDs is based on the strength of the crypto/rand // package. diff --git a/vendor/github.com/codahale/aesnicheck/LICENSE b/vendor/github.com/klauspost/cpuid/LICENSE similarity index 89% rename from vendor/github.com/codahale/aesnicheck/LICENSE rename to vendor/github.com/klauspost/cpuid/LICENSE index f9835c241..5cec7ee94 100644 --- a/vendor/github.com/codahale/aesnicheck/LICENSE +++ b/vendor/github.com/klauspost/cpuid/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Coda Hale +Copyright (c) 2015 Klaus Post Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,14 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/klauspost/cpuid/cpuid.go b/vendor/github.com/klauspost/cpuid/cpuid.go new file mode 100644 index 000000000..44e50946f --- /dev/null +++ b/vendor/github.com/klauspost/cpuid/cpuid.go @@ -0,0 +1,1030 @@ +// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. + +// Package cpuid provides information about the CPU running the current program. +// +// CPU features are detected on startup, and kept for fast access through the life of the application. +// Currently x86 / x64 (AMD64) is supported. +// +// You can access the CPU information by accessing the shared CPU variable of the cpuid library. +// +// Package home: https://github.com/klauspost/cpuid +package cpuid + +import "strings" + +// Vendor is a representation of a CPU vendor. +type Vendor int + +const ( + Other Vendor = iota + Intel + AMD + VIA + Transmeta + NSC + KVM // Kernel-based Virtual Machine + MSVM // Microsoft Hyper-V or Windows Virtual PC + VMware + XenHVM +) + +const ( + CMOV = 1 << iota // i686 CMOV + NX // NX (No-Execute) bit + AMD3DNOW // AMD 3DNOW + AMD3DNOWEXT // AMD 3DNowExt + MMX // standard MMX + MMXEXT // SSE integer functions or AMD MMX ext + SSE // SSE functions + SSE2 // P4 SSE functions + SSE3 // Prescott SSE3 functions + SSSE3 // Conroe SSSE3 functions + SSE4 // Penryn SSE4.1 functions + SSE4A // AMD Barcelona microarchitecture SSE4a instructions + SSE42 // Nehalem SSE4.2 functions + AVX // AVX functions + AVX2 // AVX2 functions + FMA3 // Intel FMA 3 + FMA4 // Bulldozer FMA4 functions + XOP // Bulldozer XOP functions + F16C // Half-precision floating-point conversion + BMI1 // Bit Manipulation Instruction Set 1 + BMI2 // Bit Manipulation Instruction Set 2 + TBM // AMD Trailing Bit Manipulation + LZCNT // LZCNT instruction + POPCNT // POPCNT instruction + AESNI // Advanced Encryption Standard New Instructions + CLMUL // Carry-less Multiplication + HTT // Hyperthreading (enabled) + HLE // Hardware Lock Elision + RTM // Restricted Transactional Memory + RDRAND // RDRAND instruction is available + RDSEED // RDSEED instruction is available + ADX // Intel ADX (Multi-Precision Add-Carry Instruction Extensions) + SHA // Intel SHA Extensions + AVX512F // AVX-512 Foundation + AVX512DQ // AVX-512 Doubleword and Quadword Instructions + AVX512IFMA // AVX-512 Integer Fused Multiply-Add Instructions + AVX512PF // AVX-512 Prefetch Instructions + AVX512ER // AVX-512 Exponential and Reciprocal Instructions + AVX512CD // AVX-512 Conflict Detection Instructions + AVX512BW // AVX-512 Byte and Word Instructions + AVX512VL // AVX-512 Vector Length Extensions + AVX512VBMI // AVX-512 Vector Bit Manipulation Instructions + MPX // Intel MPX (Memory Protection Extensions) + ERMS // Enhanced REP MOVSB/STOSB + RDTSCP // RDTSCP Instruction + CX16 // CMPXCHG16B Instruction + SGX // Software Guard Extensions + + // Performance indicators + SSE2SLOW // SSE2 is supported, but usually not faster + SSE3SLOW // SSE3 is supported, but usually not faster + ATOM // Atom processor, some SSSE3 instructions are slower +) + +var flagNames = map[Flags]string{ + CMOV: "CMOV", // i686 CMOV + NX: "NX", // NX (No-Execute) bit + AMD3DNOW: "AMD3DNOW", // AMD 3DNOW + AMD3DNOWEXT: "AMD3DNOWEXT", // AMD 3DNowExt + MMX: "MMX", // Standard MMX + MMXEXT: "MMXEXT", // SSE integer functions or AMD MMX ext + SSE: "SSE", // SSE functions + SSE2: "SSE2", // P4 SSE2 functions + SSE3: "SSE3", // Prescott SSE3 functions + SSSE3: "SSSE3", // Conroe SSSE3 functions + SSE4: "SSE4.1", // Penryn SSE4.1 functions + SSE4A: "SSE4A", // AMD Barcelona microarchitecture SSE4a instructions + SSE42: "SSE4.2", // Nehalem SSE4.2 functions + AVX: "AVX", // AVX functions + AVX2: "AVX2", // AVX functions + FMA3: "FMA3", // Intel FMA 3 + FMA4: "FMA4", // Bulldozer FMA4 functions + XOP: "XOP", // Bulldozer XOP functions + F16C: "F16C", // Half-precision floating-point conversion + BMI1: "BMI1", // Bit Manipulation Instruction Set 1 + BMI2: "BMI2", // Bit Manipulation Instruction Set 2 + TBM: "TBM", // AMD Trailing Bit Manipulation + LZCNT: "LZCNT", // LZCNT instruction + POPCNT: "POPCNT", // POPCNT instruction + AESNI: "AESNI", // Advanced Encryption Standard New Instructions + CLMUL: "CLMUL", // Carry-less Multiplication + HTT: "HTT", // Hyperthreading (enabled) + HLE: "HLE", // Hardware Lock Elision + RTM: "RTM", // Restricted Transactional Memory + RDRAND: "RDRAND", // RDRAND instruction is available + RDSEED: "RDSEED", // RDSEED instruction is available + ADX: "ADX", // Intel ADX (Multi-Precision Add-Carry Instruction Extensions) + SHA: "SHA", // Intel SHA Extensions + AVX512F: "AVX512F", // AVX-512 Foundation + AVX512DQ: "AVX512DQ", // AVX-512 Doubleword and Quadword Instructions + AVX512IFMA: "AVX512IFMA", // AVX-512 Integer Fused Multiply-Add Instructions + AVX512PF: "AVX512PF", // AVX-512 Prefetch Instructions + AVX512ER: "AVX512ER", // AVX-512 Exponential and Reciprocal Instructions + AVX512CD: "AVX512CD", // AVX-512 Conflict Detection Instructions + AVX512BW: "AVX512BW", // AVX-512 Byte and Word Instructions + AVX512VL: "AVX512VL", // AVX-512 Vector Length Extensions + AVX512VBMI: "AVX512VBMI", // AVX-512 Vector Bit Manipulation Instructions + MPX: "MPX", // Intel MPX (Memory Protection Extensions) + ERMS: "ERMS", // Enhanced REP MOVSB/STOSB + RDTSCP: "RDTSCP", // RDTSCP Instruction + CX16: "CX16", // CMPXCHG16B Instruction + SGX: "SGX", // Software Guard Extensions + + // Performance indicators + SSE2SLOW: "SSE2SLOW", // SSE2 supported, but usually not faster + SSE3SLOW: "SSE3SLOW", // SSE3 supported, but usually not faster + ATOM: "ATOM", // Atom processor, some SSSE3 instructions are slower + +} + +// CPUInfo contains information about the detected system CPU. +type CPUInfo struct { + BrandName string // Brand name reported by the CPU + VendorID Vendor // Comparable CPU vendor ID + Features Flags // Features of the CPU + PhysicalCores int // Number of physical processor cores in your CPU. Will be 0 if undetectable. + ThreadsPerCore int // Number of threads per physical core. Will be 1 if undetectable. + LogicalCores int // Number of physical cores times threads that can run on each core through the use of hyperthreading. Will be 0 if undetectable. + Family int // CPU family number + Model int // CPU model number + CacheLine int // Cache line size in bytes. Will be 0 if undetectable. + Cache struct { + L1I int // L1 Instruction Cache (per core or shared). Will be -1 if undetected + L1D int // L1 Data Cache (per core or shared). Will be -1 if undetected + L2 int // L2 Cache (per core or shared). Will be -1 if undetected + L3 int // L3 Instruction Cache (per core or shared). Will be -1 if undetected + } + SGX SGXSupport + maxFunc uint32 + maxExFunc uint32 +} + +var cpuid func(op uint32) (eax, ebx, ecx, edx uint32) +var cpuidex func(op, op2 uint32) (eax, ebx, ecx, edx uint32) +var xgetbv func(index uint32) (eax, edx uint32) +var rdtscpAsm func() (eax, ebx, ecx, edx uint32) + +// CPU contains information about the CPU as detected on startup, +// or when Detect last was called. +// +// Use this as the primary entry point to you data, +// this way queries are +var CPU CPUInfo + +func init() { + initCPU() + Detect() +} + +// Detect will re-detect current CPU info. +// This will replace the content of the exported CPU variable. +// +// Unless you expect the CPU to change while you are running your program +// you should not need to call this function. +// If you call this, you must ensure that no other goroutine is accessing the +// exported CPU variable. +func Detect() { + CPU.maxFunc = maxFunctionID() + CPU.maxExFunc = maxExtendedFunction() + CPU.BrandName = brandName() + CPU.CacheLine = cacheLine() + CPU.Family, CPU.Model = familyModel() + CPU.Features = support() + CPU.SGX = hasSGX(CPU.Features&SGX != 0) + CPU.ThreadsPerCore = threadsPerCore() + CPU.LogicalCores = logicalCores() + CPU.PhysicalCores = physicalCores() + CPU.VendorID = vendorID() + CPU.cacheSize() +} + +// Generated here: http://play.golang.org/p/BxFH2Gdc0G + +// Cmov indicates support of CMOV instructions +func (c CPUInfo) Cmov() bool { + return c.Features&CMOV != 0 +} + +// Amd3dnow indicates support of AMD 3DNOW! instructions +func (c CPUInfo) Amd3dnow() bool { + return c.Features&AMD3DNOW != 0 +} + +// Amd3dnowExt indicates support of AMD 3DNOW! Extended instructions +func (c CPUInfo) Amd3dnowExt() bool { + return c.Features&AMD3DNOWEXT != 0 +} + +// MMX indicates support of MMX instructions +func (c CPUInfo) MMX() bool { + return c.Features&MMX != 0 +} + +// MMXExt indicates support of MMXEXT instructions +// (SSE integer functions or AMD MMX ext) +func (c CPUInfo) MMXExt() bool { + return c.Features&MMXEXT != 0 +} + +// SSE indicates support of SSE instructions +func (c CPUInfo) SSE() bool { + return c.Features&SSE != 0 +} + +// SSE2 indicates support of SSE 2 instructions +func (c CPUInfo) SSE2() bool { + return c.Features&SSE2 != 0 +} + +// SSE3 indicates support of SSE 3 instructions +func (c CPUInfo) SSE3() bool { + return c.Features&SSE3 != 0 +} + +// SSSE3 indicates support of SSSE 3 instructions +func (c CPUInfo) SSSE3() bool { + return c.Features&SSSE3 != 0 +} + +// SSE4 indicates support of SSE 4 (also called SSE 4.1) instructions +func (c CPUInfo) SSE4() bool { + return c.Features&SSE4 != 0 +} + +// SSE42 indicates support of SSE4.2 instructions +func (c CPUInfo) SSE42() bool { + return c.Features&SSE42 != 0 +} + +// AVX indicates support of AVX instructions +// and operating system support of AVX instructions +func (c CPUInfo) AVX() bool { + return c.Features&AVX != 0 +} + +// AVX2 indicates support of AVX2 instructions +func (c CPUInfo) AVX2() bool { + return c.Features&AVX2 != 0 +} + +// FMA3 indicates support of FMA3 instructions +func (c CPUInfo) FMA3() bool { + return c.Features&FMA3 != 0 +} + +// FMA4 indicates support of FMA4 instructions +func (c CPUInfo) FMA4() bool { + return c.Features&FMA4 != 0 +} + +// XOP indicates support of XOP instructions +func (c CPUInfo) XOP() bool { + return c.Features&XOP != 0 +} + +// F16C indicates support of F16C instructions +func (c CPUInfo) F16C() bool { + return c.Features&F16C != 0 +} + +// BMI1 indicates support of BMI1 instructions +func (c CPUInfo) BMI1() bool { + return c.Features&BMI1 != 0 +} + +// BMI2 indicates support of BMI2 instructions +func (c CPUInfo) BMI2() bool { + return c.Features&BMI2 != 0 +} + +// TBM indicates support of TBM instructions +// (AMD Trailing Bit Manipulation) +func (c CPUInfo) TBM() bool { + return c.Features&TBM != 0 +} + +// Lzcnt indicates support of LZCNT instruction +func (c CPUInfo) Lzcnt() bool { + return c.Features&LZCNT != 0 +} + +// Popcnt indicates support of POPCNT instruction +func (c CPUInfo) Popcnt() bool { + return c.Features&POPCNT != 0 +} + +// HTT indicates the processor has Hyperthreading enabled +func (c CPUInfo) HTT() bool { + return c.Features&HTT != 0 +} + +// SSE2Slow indicates that SSE2 may be slow on this processor +func (c CPUInfo) SSE2Slow() bool { + return c.Features&SSE2SLOW != 0 +} + +// SSE3Slow indicates that SSE3 may be slow on this processor +func (c CPUInfo) SSE3Slow() bool { + return c.Features&SSE3SLOW != 0 +} + +// AesNi indicates support of AES-NI instructions +// (Advanced Encryption Standard New Instructions) +func (c CPUInfo) AesNi() bool { + return c.Features&AESNI != 0 +} + +// Clmul indicates support of CLMUL instructions +// (Carry-less Multiplication) +func (c CPUInfo) Clmul() bool { + return c.Features&CLMUL != 0 +} + +// NX indicates support of NX (No-Execute) bit +func (c CPUInfo) NX() bool { + return c.Features&NX != 0 +} + +// SSE4A indicates support of AMD Barcelona microarchitecture SSE4a instructions +func (c CPUInfo) SSE4A() bool { + return c.Features&SSE4A != 0 +} + +// HLE indicates support of Hardware Lock Elision +func (c CPUInfo) HLE() bool { + return c.Features&HLE != 0 +} + +// RTM indicates support of Restricted Transactional Memory +func (c CPUInfo) RTM() bool { + return c.Features&RTM != 0 +} + +// Rdrand indicates support of RDRAND instruction is available +func (c CPUInfo) Rdrand() bool { + return c.Features&RDRAND != 0 +} + +// Rdseed indicates support of RDSEED instruction is available +func (c CPUInfo) Rdseed() bool { + return c.Features&RDSEED != 0 +} + +// ADX indicates support of Intel ADX (Multi-Precision Add-Carry Instruction Extensions) +func (c CPUInfo) ADX() bool { + return c.Features&ADX != 0 +} + +// SHA indicates support of Intel SHA Extensions +func (c CPUInfo) SHA() bool { + return c.Features&SHA != 0 +} + +// AVX512F indicates support of AVX-512 Foundation +func (c CPUInfo) AVX512F() bool { + return c.Features&AVX512F != 0 +} + +// AVX512DQ indicates support of AVX-512 Doubleword and Quadword Instructions +func (c CPUInfo) AVX512DQ() bool { + return c.Features&AVX512DQ != 0 +} + +// AVX512IFMA indicates support of AVX-512 Integer Fused Multiply-Add Instructions +func (c CPUInfo) AVX512IFMA() bool { + return c.Features&AVX512IFMA != 0 +} + +// AVX512PF indicates support of AVX-512 Prefetch Instructions +func (c CPUInfo) AVX512PF() bool { + return c.Features&AVX512PF != 0 +} + +// AVX512ER indicates support of AVX-512 Exponential and Reciprocal Instructions +func (c CPUInfo) AVX512ER() bool { + return c.Features&AVX512ER != 0 +} + +// AVX512CD indicates support of AVX-512 Conflict Detection Instructions +func (c CPUInfo) AVX512CD() bool { + return c.Features&AVX512CD != 0 +} + +// AVX512BW indicates support of AVX-512 Byte and Word Instructions +func (c CPUInfo) AVX512BW() bool { + return c.Features&AVX512BW != 0 +} + +// AVX512VL indicates support of AVX-512 Vector Length Extensions +func (c CPUInfo) AVX512VL() bool { + return c.Features&AVX512VL != 0 +} + +// AVX512VBMI indicates support of AVX-512 Vector Bit Manipulation Instructions +func (c CPUInfo) AVX512VBMI() bool { + return c.Features&AVX512VBMI != 0 +} + +// MPX indicates support of Intel MPX (Memory Protection Extensions) +func (c CPUInfo) MPX() bool { + return c.Features&MPX != 0 +} + +// ERMS indicates support of Enhanced REP MOVSB/STOSB +func (c CPUInfo) ERMS() bool { + return c.Features&ERMS != 0 +} + +// RDTSCP Instruction is available. +func (c CPUInfo) RDTSCP() bool { + return c.Features&RDTSCP != 0 +} + +// CX16 indicates if CMPXCHG16B instruction is available. +func (c CPUInfo) CX16() bool { + return c.Features&CX16 != 0 +} + +// TSX is split into HLE (Hardware Lock Elision) and RTM (Restricted Transactional Memory) detection. +// So TSX simply checks that. +func (c CPUInfo) TSX() bool { + return c.Features&(MPX|RTM) == MPX|RTM +} + +// Atom indicates an Atom processor +func (c CPUInfo) Atom() bool { + return c.Features&ATOM != 0 +} + +// Intel returns true if vendor is recognized as Intel +func (c CPUInfo) Intel() bool { + return c.VendorID == Intel +} + +// AMD returns true if vendor is recognized as AMD +func (c CPUInfo) AMD() bool { + return c.VendorID == AMD +} + +// Transmeta returns true if vendor is recognized as Transmeta +func (c CPUInfo) Transmeta() bool { + return c.VendorID == Transmeta +} + +// NSC returns true if vendor is recognized as National Semiconductor +func (c CPUInfo) NSC() bool { + return c.VendorID == NSC +} + +// VIA returns true if vendor is recognized as VIA +func (c CPUInfo) VIA() bool { + return c.VendorID == VIA +} + +// RTCounter returns the 64-bit time-stamp counter +// Uses the RDTSCP instruction. The value 0 is returned +// if the CPU does not support the instruction. +func (c CPUInfo) RTCounter() uint64 { + if !c.RDTSCP() { + return 0 + } + a, _, _, d := rdtscpAsm() + return uint64(a) | (uint64(d) << 32) +} + +// Ia32TscAux returns the IA32_TSC_AUX part of the RDTSCP. +// This variable is OS dependent, but on Linux contains information +// about the current cpu/core the code is running on. +// If the RDTSCP instruction isn't supported on the CPU, the value 0 is returned. +func (c CPUInfo) Ia32TscAux() uint32 { + if !c.RDTSCP() { + return 0 + } + _, _, ecx, _ := rdtscpAsm() + return ecx +} + +// LogicalCPU will return the Logical CPU the code is currently executing on. +// This is likely to change when the OS re-schedules the running thread +// to another CPU. +// If the current core cannot be detected, -1 will be returned. +func (c CPUInfo) LogicalCPU() int { + if c.maxFunc < 1 { + return -1 + } + _, ebx, _, _ := cpuid(1) + return int(ebx >> 24) +} + +// VM Will return true if the cpu id indicates we are in +// a virtual machine. This is only a hint, and will very likely +// have many false negatives. +func (c CPUInfo) VM() bool { + switch c.VendorID { + case MSVM, KVM, VMware, XenHVM: + return true + } + return false +} + +// Flags contains detected cpu features and caracteristics +type Flags uint64 + +// String returns a string representation of the detected +// CPU features. +func (f Flags) String() string { + return strings.Join(f.Strings(), ",") +} + +// Strings returns and array of the detected features. +func (f Flags) Strings() []string { + s := support() + r := make([]string, 0, 20) + for i := uint(0); i < 64; i++ { + key := Flags(1 << i) + val := flagNames[key] + if s&key != 0 { + r = append(r, val) + } + } + return r +} + +func maxExtendedFunction() uint32 { + eax, _, _, _ := cpuid(0x80000000) + return eax +} + +func maxFunctionID() uint32 { + a, _, _, _ := cpuid(0) + return a +} + +func brandName() string { + if maxExtendedFunction() >= 0x80000004 { + v := make([]uint32, 0, 48) + for i := uint32(0); i < 3; i++ { + a, b, c, d := cpuid(0x80000002 + i) + v = append(v, a, b, c, d) + } + return strings.Trim(string(valAsString(v...)), " ") + } + return "unknown" +} + +func threadsPerCore() int { + mfi := maxFunctionID() + if mfi < 0x4 || vendorID() != Intel { + return 1 + } + + if mfi < 0xb { + _, b, _, d := cpuid(1) + if (d & (1 << 28)) != 0 { + // v will contain logical core count + v := (b >> 16) & 255 + if v > 1 { + a4, _, _, _ := cpuid(4) + // physical cores + v2 := (a4 >> 26) + 1 + if v2 > 0 { + return int(v) / int(v2) + } + } + } + return 1 + } + _, b, _, _ := cpuidex(0xb, 0) + if b&0xffff == 0 { + return 1 + } + return int(b & 0xffff) +} + +func logicalCores() int { + mfi := maxFunctionID() + switch vendorID() { + case Intel: + // Use this on old Intel processors + if mfi < 0xb { + if mfi < 1 { + return 0 + } + // CPUID.1:EBX[23:16] represents the maximum number of addressable IDs (initial APIC ID) + // that can be assigned to logical processors in a physical package. + // The value may not be the same as the number of logical processors that are present in the hardware of a physical package. + _, ebx, _, _ := cpuid(1) + logical := (ebx >> 16) & 0xff + return int(logical) + } + _, b, _, _ := cpuidex(0xb, 1) + return int(b & 0xffff) + case AMD: + _, b, _, _ := cpuid(1) + return int((b >> 16) & 0xff) + default: + return 0 + } +} + +func familyModel() (int, int) { + if maxFunctionID() < 0x1 { + return 0, 0 + } + eax, _, _, _ := cpuid(1) + family := ((eax >> 8) & 0xf) + ((eax >> 20) & 0xff) + model := ((eax >> 4) & 0xf) + ((eax >> 12) & 0xf0) + return int(family), int(model) +} + +func physicalCores() int { + switch vendorID() { + case Intel: + return logicalCores() / threadsPerCore() + case AMD: + if maxExtendedFunction() >= 0x80000008 { + _, _, c, _ := cpuid(0x80000008) + return int(c&0xff) + 1 + } + } + return 0 +} + +// Except from http://en.wikipedia.org/wiki/CPUID#EAX.3D0:_Get_vendor_ID +var vendorMapping = map[string]Vendor{ + "AMDisbetter!": AMD, + "AuthenticAMD": AMD, + "CentaurHauls": VIA, + "GenuineIntel": Intel, + "TransmetaCPU": Transmeta, + "GenuineTMx86": Transmeta, + "Geode by NSC": NSC, + "VIA VIA VIA ": VIA, + "KVMKVMKVMKVM": KVM, + "Microsoft Hv": MSVM, + "VMwareVMware": VMware, + "XenVMMXenVMM": XenHVM, +} + +func vendorID() Vendor { + _, b, c, d := cpuid(0) + v := valAsString(b, d, c) + vend, ok := vendorMapping[string(v)] + if !ok { + return Other + } + return vend +} + +func cacheLine() int { + if maxFunctionID() < 0x1 { + return 0 + } + + _, ebx, _, _ := cpuid(1) + cache := (ebx & 0xff00) >> 5 // cflush size + if cache == 0 && maxExtendedFunction() >= 0x80000006 { + _, _, ecx, _ := cpuid(0x80000006) + cache = ecx & 0xff // cacheline size + } + // TODO: Read from Cache and TLB Information + return int(cache) +} + +func (c *CPUInfo) cacheSize() { + c.Cache.L1D = -1 + c.Cache.L1I = -1 + c.Cache.L2 = -1 + c.Cache.L3 = -1 + vendor := vendorID() + switch vendor { + case Intel: + if maxFunctionID() < 4 { + return + } + for i := uint32(0); ; i++ { + eax, ebx, ecx, _ := cpuidex(4, i) + cacheType := eax & 15 + if cacheType == 0 { + break + } + cacheLevel := (eax >> 5) & 7 + coherency := int(ebx&0xfff) + 1 + partitions := int((ebx>>12)&0x3ff) + 1 + associativity := int((ebx>>22)&0x3ff) + 1 + sets := int(ecx) + 1 + size := associativity * partitions * coherency * sets + switch cacheLevel { + case 1: + if cacheType == 1 { + // 1 = Data Cache + c.Cache.L1D = size + } else if cacheType == 2 { + // 2 = Instruction Cache + c.Cache.L1I = size + } else { + if c.Cache.L1D < 0 { + c.Cache.L1I = size + } + if c.Cache.L1I < 0 { + c.Cache.L1I = size + } + } + case 2: + c.Cache.L2 = size + case 3: + c.Cache.L3 = size + } + } + case AMD: + // Untested. + if maxExtendedFunction() < 0x80000005 { + return + } + _, _, ecx, edx := cpuid(0x80000005) + c.Cache.L1D = int(((ecx >> 24) & 0xFF) * 1024) + c.Cache.L1I = int(((edx >> 24) & 0xFF) * 1024) + + if maxExtendedFunction() < 0x80000006 { + return + } + _, _, ecx, _ = cpuid(0x80000006) + c.Cache.L2 = int(((ecx >> 16) & 0xFFFF) * 1024) + } + + return +} + +type SGXSupport struct { + Available bool + SGX1Supported bool + SGX2Supported bool + MaxEnclaveSizeNot64 int64 + MaxEnclaveSize64 int64 +} + +func hasSGX(available bool) (rval SGXSupport) { + rval.Available = available + + if !available { + return + } + + a, _, _, d := cpuidex(0x12, 0) + rval.SGX1Supported = a&0x01 != 0 + rval.SGX2Supported = a&0x02 != 0 + rval.MaxEnclaveSizeNot64 = 1 << (d & 0xFF) // pow 2 + rval.MaxEnclaveSize64 = 1 << ((d >> 8) & 0xFF) // pow 2 + + return +} + +func support() Flags { + mfi := maxFunctionID() + vend := vendorID() + if mfi < 0x1 { + return 0 + } + rval := uint64(0) + _, _, c, d := cpuid(1) + if (d & (1 << 15)) != 0 { + rval |= CMOV + } + if (d & (1 << 23)) != 0 { + rval |= MMX + } + if (d & (1 << 25)) != 0 { + rval |= MMXEXT + } + if (d & (1 << 25)) != 0 { + rval |= SSE + } + if (d & (1 << 26)) != 0 { + rval |= SSE2 + } + if (c & 1) != 0 { + rval |= SSE3 + } + if (c & 0x00000200) != 0 { + rval |= SSSE3 + } + if (c & 0x00080000) != 0 { + rval |= SSE4 + } + if (c & 0x00100000) != 0 { + rval |= SSE42 + } + if (c & (1 << 25)) != 0 { + rval |= AESNI + } + if (c & (1 << 1)) != 0 { + rval |= CLMUL + } + if c&(1<<23) != 0 { + rval |= POPCNT + } + if c&(1<<30) != 0 { + rval |= RDRAND + } + if c&(1<<29) != 0 { + rval |= F16C + } + if c&(1<<13) != 0 { + rval |= CX16 + } + if vend == Intel && (d&(1<<28)) != 0 && mfi >= 4 { + if threadsPerCore() > 1 { + rval |= HTT + } + } + + // Check XGETBV, OXSAVE and AVX bits + if c&(1<<26) != 0 && c&(1<<27) != 0 && c&(1<<28) != 0 { + // Check for OS support + eax, _ := xgetbv(0) + if (eax & 0x6) == 0x6 { + rval |= AVX + if (c & 0x00001000) != 0 { + rval |= FMA3 + } + } + } + + // Check AVX2, AVX2 requires OS support, but BMI1/2 don't. + if mfi >= 7 { + _, ebx, ecx, _ := cpuidex(7, 0) + if (rval&AVX) != 0 && (ebx&0x00000020) != 0 { + rval |= AVX2 + } + if (ebx & 0x00000008) != 0 { + rval |= BMI1 + if (ebx & 0x00000100) != 0 { + rval |= BMI2 + } + } + if ebx&(1<<2) != 0 { + rval |= SGX + } + if ebx&(1<<4) != 0 { + rval |= HLE + } + if ebx&(1<<9) != 0 { + rval |= ERMS + } + if ebx&(1<<11) != 0 { + rval |= RTM + } + if ebx&(1<<14) != 0 { + rval |= MPX + } + if ebx&(1<<18) != 0 { + rval |= RDSEED + } + if ebx&(1<<19) != 0 { + rval |= ADX + } + if ebx&(1<<29) != 0 { + rval |= SHA + } + + // Only detect AVX-512 features if XGETBV is supported + if c&((1<<26)|(1<<27)) == (1<<26)|(1<<27) { + // Check for OS support + eax, _ := xgetbv(0) + + // Verify that XCR0[7:5] = ‘111b’ (OPMASK state, upper 256-bit of ZMM0-ZMM15 and + // ZMM16-ZMM31 state are enabled by OS) + /// and that XCR0[2:1] = ‘11b’ (XMM state and YMM state are enabled by OS). + if (eax>>5)&7 == 7 && (eax>>1)&3 == 3 { + if ebx&(1<<16) != 0 { + rval |= AVX512F + } + if ebx&(1<<17) != 0 { + rval |= AVX512DQ + } + if ebx&(1<<21) != 0 { + rval |= AVX512IFMA + } + if ebx&(1<<26) != 0 { + rval |= AVX512PF + } + if ebx&(1<<27) != 0 { + rval |= AVX512ER + } + if ebx&(1<<28) != 0 { + rval |= AVX512CD + } + if ebx&(1<<30) != 0 { + rval |= AVX512BW + } + if ebx&(1<<31) != 0 { + rval |= AVX512VL + } + // ecx + if ecx&(1<<1) != 0 { + rval |= AVX512VBMI + } + } + } + } + + if maxExtendedFunction() >= 0x80000001 { + _, _, c, d := cpuid(0x80000001) + if (c & (1 << 5)) != 0 { + rval |= LZCNT + rval |= POPCNT + } + if (d & (1 << 31)) != 0 { + rval |= AMD3DNOW + } + if (d & (1 << 30)) != 0 { + rval |= AMD3DNOWEXT + } + if (d & (1 << 23)) != 0 { + rval |= MMX + } + if (d & (1 << 22)) != 0 { + rval |= MMXEXT + } + if (c & (1 << 6)) != 0 { + rval |= SSE4A + } + if d&(1<<20) != 0 { + rval |= NX + } + if d&(1<<27) != 0 { + rval |= RDTSCP + } + + /* Allow for selectively disabling SSE2 functions on AMD processors + with SSE2 support but not SSE4a. This includes Athlon64, some + Opteron, and some Sempron processors. MMX, SSE, or 3DNow! are faster + than SSE2 often enough to utilize this special-case flag. + AV_CPU_FLAG_SSE2 and AV_CPU_FLAG_SSE2SLOW are both set in this case + so that SSE2 is used unless explicitly disabled by checking + AV_CPU_FLAG_SSE2SLOW. */ + if vendorID() != Intel && + rval&SSE2 != 0 && (c&0x00000040) == 0 { + rval |= SSE2SLOW + } + + /* XOP and FMA4 use the AVX instruction coding scheme, so they can't be + * used unless the OS has AVX support. */ + if (rval & AVX) != 0 { + if (c & 0x00000800) != 0 { + rval |= XOP + } + if (c & 0x00010000) != 0 { + rval |= FMA4 + } + } + + if vendorID() == Intel { + family, model := familyModel() + if family == 6 && (model == 9 || model == 13 || model == 14) { + /* 6/9 (pentium-m "banias"), 6/13 (pentium-m "dothan"), and + * 6/14 (core1 "yonah") theoretically support sse2, but it's + * usually slower than mmx. */ + if (rval & SSE2) != 0 { + rval |= SSE2SLOW + } + if (rval & SSE3) != 0 { + rval |= SSE3SLOW + } + } + /* The Atom processor has SSSE3 support, which is useful in many cases, + * but sometimes the SSSE3 version is slower than the SSE2 equivalent + * on the Atom, but is generally faster on other processors supporting + * SSSE3. This flag allows for selectively disabling certain SSSE3 + * functions on the Atom. */ + if family == 6 && model == 28 { + rval |= ATOM + } + } + } + return Flags(rval) +} + +func valAsString(values ...uint32) []byte { + r := make([]byte, 4*len(values)) + for i, v := range values { + dst := r[i*4:] + dst[0] = byte(v & 0xff) + dst[1] = byte((v >> 8) & 0xff) + dst[2] = byte((v >> 16) & 0xff) + dst[3] = byte((v >> 24) & 0xff) + switch { + case dst[0] == 0: + return r[:i*4] + case dst[1] == 0: + return r[:i*4+1] + case dst[2] == 0: + return r[:i*4+2] + case dst[3] == 0: + return r[:i*4+3] + } + } + return r +} diff --git a/vendor/github.com/klauspost/cpuid/cpuid_386.s b/vendor/github.com/klauspost/cpuid/cpuid_386.s new file mode 100644 index 000000000..4d731711e --- /dev/null +++ b/vendor/github.com/klauspost/cpuid/cpuid_386.s @@ -0,0 +1,42 @@ +// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. + +// +build 386,!gccgo + +// func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) +TEXT ·asmCpuid(SB), 7, $0 + XORL CX, CX + MOVL op+0(FP), AX + CPUID + MOVL AX, eax+4(FP) + MOVL BX, ebx+8(FP) + MOVL CX, ecx+12(FP) + MOVL DX, edx+16(FP) + RET + +// func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32) +TEXT ·asmCpuidex(SB), 7, $0 + MOVL op+0(FP), AX + MOVL op2+4(FP), CX + CPUID + MOVL AX, eax+8(FP) + MOVL BX, ebx+12(FP) + MOVL CX, ecx+16(FP) + MOVL DX, edx+20(FP) + RET + +// func xgetbv(index uint32) (eax, edx uint32) +TEXT ·asmXgetbv(SB), 7, $0 + MOVL index+0(FP), CX + BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV + MOVL AX, eax+4(FP) + MOVL DX, edx+8(FP) + RET + +// func asmRdtscpAsm() (eax, ebx, ecx, edx uint32) +TEXT ·asmRdtscpAsm(SB), 7, $0 + BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP + MOVL AX, eax+0(FP) + MOVL BX, ebx+4(FP) + MOVL CX, ecx+8(FP) + MOVL DX, edx+12(FP) + RET diff --git a/vendor/github.com/klauspost/cpuid/cpuid_amd64.s b/vendor/github.com/klauspost/cpuid/cpuid_amd64.s new file mode 100644 index 000000000..3c1d60e42 --- /dev/null +++ b/vendor/github.com/klauspost/cpuid/cpuid_amd64.s @@ -0,0 +1,42 @@ +// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. + +//+build amd64,!gccgo + +// func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) +TEXT ·asmCpuid(SB), 7, $0 + XORQ CX, CX + MOVL op+0(FP), AX + CPUID + MOVL AX, eax+8(FP) + MOVL BX, ebx+12(FP) + MOVL CX, ecx+16(FP) + MOVL DX, edx+20(FP) + RET + +// func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32) +TEXT ·asmCpuidex(SB), 7, $0 + MOVL op+0(FP), AX + MOVL op2+4(FP), CX + CPUID + MOVL AX, eax+8(FP) + MOVL BX, ebx+12(FP) + MOVL CX, ecx+16(FP) + MOVL DX, edx+20(FP) + RET + +// func asmXgetbv(index uint32) (eax, edx uint32) +TEXT ·asmXgetbv(SB), 7, $0 + MOVL index+0(FP), CX + BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV + MOVL AX, eax+8(FP) + MOVL DX, edx+12(FP) + RET + +// func asmRdtscpAsm() (eax, ebx, ecx, edx uint32) +TEXT ·asmRdtscpAsm(SB), 7, $0 + BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP + MOVL AX, eax+0(FP) + MOVL BX, ebx+4(FP) + MOVL CX, ecx+8(FP) + MOVL DX, edx+12(FP) + RET diff --git a/vendor/github.com/klauspost/cpuid/detect_intel.go b/vendor/github.com/klauspost/cpuid/detect_intel.go new file mode 100644 index 000000000..a5f04dd6d --- /dev/null +++ b/vendor/github.com/klauspost/cpuid/detect_intel.go @@ -0,0 +1,17 @@ +// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. + +// +build 386,!gccgo amd64,!gccgo + +package cpuid + +func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) +func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32) +func asmXgetbv(index uint32) (eax, edx uint32) +func asmRdtscpAsm() (eax, ebx, ecx, edx uint32) + +func initCPU() { + cpuid = asmCpuid + cpuidex = asmCpuidex + xgetbv = asmXgetbv + rdtscpAsm = asmRdtscpAsm +} diff --git a/vendor/github.com/klauspost/cpuid/detect_ref.go b/vendor/github.com/klauspost/cpuid/detect_ref.go new file mode 100644 index 000000000..909c5d9a7 --- /dev/null +++ b/vendor/github.com/klauspost/cpuid/detect_ref.go @@ -0,0 +1,23 @@ +// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. + +// +build !amd64,!386 gccgo + +package cpuid + +func initCPU() { + cpuid = func(op uint32) (eax, ebx, ecx, edx uint32) { + return 0, 0, 0, 0 + } + + cpuidex = func(op, op2 uint32) (eax, ebx, ecx, edx uint32) { + return 0, 0, 0, 0 + } + + xgetbv = func(index uint32) (eax, edx uint32) { + return 0, 0 + } + + rdtscpAsm = func() (eax, ebx, ecx, edx uint32) { + return 0, 0, 0, 0 + } +} diff --git a/vendor/github.com/klauspost/cpuid/generate.go b/vendor/github.com/klauspost/cpuid/generate.go new file mode 100644 index 000000000..90e7a98d2 --- /dev/null +++ b/vendor/github.com/klauspost/cpuid/generate.go @@ -0,0 +1,4 @@ +package cpuid + +//go:generate go run private-gen.go +//go:generate gofmt -w ./private diff --git a/vendor/github.com/klauspost/cpuid/private-gen.go b/vendor/github.com/klauspost/cpuid/private-gen.go new file mode 100644 index 000000000..437333d29 --- /dev/null +++ b/vendor/github.com/klauspost/cpuid/private-gen.go @@ -0,0 +1,476 @@ +// +build ignore + +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "io" + "io/ioutil" + "log" + "os" + "reflect" + "strings" + "unicode" + "unicode/utf8" +) + +var inFiles = []string{"cpuid.go", "cpuid_test.go"} +var copyFiles = []string{"cpuid_amd64.s", "cpuid_386.s", "detect_ref.go", "detect_intel.go"} +var fileSet = token.NewFileSet() +var reWrites = []rewrite{ + initRewrite("CPUInfo -> cpuInfo"), + initRewrite("Vendor -> vendor"), + initRewrite("Flags -> flags"), + initRewrite("Detect -> detect"), + initRewrite("CPU -> cpu"), +} +var excludeNames = map[string]bool{"string": true, "join": true, "trim": true, + // cpuid_test.go + "t": true, "println": true, "logf": true, "log": true, "fatalf": true, "fatal": true, +} + +var excludePrefixes = []string{"test", "benchmark"} + +func main() { + Package := "private" + parserMode := parser.ParseComments + exported := make(map[string]rewrite) + for _, file := range inFiles { + in, err := os.Open(file) + if err != nil { + log.Fatalf("opening input", err) + } + + src, err := ioutil.ReadAll(in) + if err != nil { + log.Fatalf("reading input", err) + } + + astfile, err := parser.ParseFile(fileSet, file, src, parserMode) + if err != nil { + log.Fatalf("parsing input", err) + } + + for _, rw := range reWrites { + astfile = rw(astfile) + } + + // Inspect the AST and print all identifiers and literals. + var startDecl token.Pos + var endDecl token.Pos + ast.Inspect(astfile, func(n ast.Node) bool { + var s string + switch x := n.(type) { + case *ast.Ident: + if x.IsExported() { + t := strings.ToLower(x.Name) + for _, pre := range excludePrefixes { + if strings.HasPrefix(t, pre) { + return true + } + } + if excludeNames[t] != true { + //if x.Pos() > startDecl && x.Pos() < endDecl { + exported[x.Name] = initRewrite(x.Name + " -> " + t) + } + } + + case *ast.GenDecl: + if x.Tok == token.CONST && x.Lparen > 0 { + startDecl = x.Lparen + endDecl = x.Rparen + // fmt.Printf("Decl:%s -> %s\n", fileSet.Position(startDecl), fileSet.Position(endDecl)) + } + } + if s != "" { + fmt.Printf("%s:\t%s\n", fileSet.Position(n.Pos()), s) + } + return true + }) + + for _, rw := range exported { + astfile = rw(astfile) + } + + var buf bytes.Buffer + + printer.Fprint(&buf, fileSet, astfile) + + // Remove package documentation and insert information + s := buf.String() + ind := strings.Index(buf.String(), "\npackage cpuid") + s = s[ind:] + s = "// Generated, DO NOT EDIT,\n" + + "// but copy it to your own project and rename the package.\n" + + "// See more at http://github.com/klauspost/cpuid\n" + + s + + outputName := Package + string(os.PathSeparator) + file + + err = ioutil.WriteFile(outputName, []byte(s), 0644) + if err != nil { + log.Fatalf("writing output: %s", err) + } + log.Println("Generated", outputName) + } + + for _, file := range copyFiles { + dst := "" + if strings.HasPrefix(file, "cpuid") { + dst = Package + string(os.PathSeparator) + file + } else { + dst = Package + string(os.PathSeparator) + "cpuid_" + file + } + err := copyFile(file, dst) + if err != nil { + log.Fatalf("copying file: %s", err) + } + log.Println("Copied", dst) + } +} + +// CopyFile copies a file from src to dst. If src and dst files exist, and are +// the same, then return success. Copy the file contents from src to dst. +func copyFile(src, dst string) (err error) { + sfi, err := os.Stat(src) + if err != nil { + return + } + if !sfi.Mode().IsRegular() { + // cannot copy non-regular files (e.g., directories, + // symlinks, devices, etc.) + return fmt.Errorf("CopyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String()) + } + dfi, err := os.Stat(dst) + if err != nil { + if !os.IsNotExist(err) { + return + } + } else { + if !(dfi.Mode().IsRegular()) { + return fmt.Errorf("CopyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String()) + } + if os.SameFile(sfi, dfi) { + return + } + } + err = copyFileContents(src, dst) + return +} + +// copyFileContents copies the contents of the file named src to the file named +// by dst. The file will be created if it does not already exist. If the +// destination file exists, all it's contents will be replaced by the contents +// of the source file. +func copyFileContents(src, dst string) (err error) { + in, err := os.Open(src) + if err != nil { + return + } + defer in.Close() + out, err := os.Create(dst) + if err != nil { + return + } + defer func() { + cerr := out.Close() + if err == nil { + err = cerr + } + }() + if _, err = io.Copy(out, in); err != nil { + return + } + err = out.Sync() + return +} + +type rewrite func(*ast.File) *ast.File + +// Mostly copied from gofmt +func initRewrite(rewriteRule string) rewrite { + f := strings.Split(rewriteRule, "->") + if len(f) != 2 { + fmt.Fprintf(os.Stderr, "rewrite rule must be of the form 'pattern -> replacement'\n") + os.Exit(2) + } + pattern := parseExpr(f[0], "pattern") + replace := parseExpr(f[1], "replacement") + return func(p *ast.File) *ast.File { return rewriteFile(pattern, replace, p) } +} + +// parseExpr parses s as an expression. +// It might make sense to expand this to allow statement patterns, +// but there are problems with preserving formatting and also +// with what a wildcard for a statement looks like. +func parseExpr(s, what string) ast.Expr { + x, err := parser.ParseExpr(s) + if err != nil { + fmt.Fprintf(os.Stderr, "parsing %s %s at %s\n", what, s, err) + os.Exit(2) + } + return x +} + +// Keep this function for debugging. +/* +func dump(msg string, val reflect.Value) { + fmt.Printf("%s:\n", msg) + ast.Print(fileSet, val.Interface()) + fmt.Println() +} +*/ + +// rewriteFile applies the rewrite rule 'pattern -> replace' to an entire file. +func rewriteFile(pattern, replace ast.Expr, p *ast.File) *ast.File { + cmap := ast.NewCommentMap(fileSet, p, p.Comments) + m := make(map[string]reflect.Value) + pat := reflect.ValueOf(pattern) + repl := reflect.ValueOf(replace) + + var rewriteVal func(val reflect.Value) reflect.Value + rewriteVal = func(val reflect.Value) reflect.Value { + // don't bother if val is invalid to start with + if !val.IsValid() { + return reflect.Value{} + } + for k := range m { + delete(m, k) + } + val = apply(rewriteVal, val) + if match(m, pat, val) { + val = subst(m, repl, reflect.ValueOf(val.Interface().(ast.Node).Pos())) + } + return val + } + + r := apply(rewriteVal, reflect.ValueOf(p)).Interface().(*ast.File) + r.Comments = cmap.Filter(r).Comments() // recreate comments list + return r +} + +// set is a wrapper for x.Set(y); it protects the caller from panics if x cannot be changed to y. +func set(x, y reflect.Value) { + // don't bother if x cannot be set or y is invalid + if !x.CanSet() || !y.IsValid() { + return + } + defer func() { + if x := recover(); x != nil { + if s, ok := x.(string); ok && + (strings.Contains(s, "type mismatch") || strings.Contains(s, "not assignable")) { + // x cannot be set to y - ignore this rewrite + return + } + panic(x) + } + }() + x.Set(y) +} + +// Values/types for special cases. +var ( + objectPtrNil = reflect.ValueOf((*ast.Object)(nil)) + scopePtrNil = reflect.ValueOf((*ast.Scope)(nil)) + + identType = reflect.TypeOf((*ast.Ident)(nil)) + objectPtrType = reflect.TypeOf((*ast.Object)(nil)) + positionType = reflect.TypeOf(token.NoPos) + callExprType = reflect.TypeOf((*ast.CallExpr)(nil)) + scopePtrType = reflect.TypeOf((*ast.Scope)(nil)) +) + +// apply replaces each AST field x in val with f(x), returning val. +// To avoid extra conversions, f operates on the reflect.Value form. +func apply(f func(reflect.Value) reflect.Value, val reflect.Value) reflect.Value { + if !val.IsValid() { + return reflect.Value{} + } + + // *ast.Objects introduce cycles and are likely incorrect after + // rewrite; don't follow them but replace with nil instead + if val.Type() == objectPtrType { + return objectPtrNil + } + + // similarly for scopes: they are likely incorrect after a rewrite; + // replace them with nil + if val.Type() == scopePtrType { + return scopePtrNil + } + + switch v := reflect.Indirect(val); v.Kind() { + case reflect.Slice: + for i := 0; i < v.Len(); i++ { + e := v.Index(i) + set(e, f(e)) + } + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + e := v.Field(i) + set(e, f(e)) + } + case reflect.Interface: + e := v.Elem() + set(v, f(e)) + } + return val +} + +func isWildcard(s string) bool { + rune, size := utf8.DecodeRuneInString(s) + return size == len(s) && unicode.IsLower(rune) +} + +// match returns true if pattern matches val, +// recording wildcard submatches in m. +// If m == nil, match checks whether pattern == val. +func match(m map[string]reflect.Value, pattern, val reflect.Value) bool { + // Wildcard matches any expression. If it appears multiple + // times in the pattern, it must match the same expression + // each time. + if m != nil && pattern.IsValid() && pattern.Type() == identType { + name := pattern.Interface().(*ast.Ident).Name + if isWildcard(name) && val.IsValid() { + // wildcards only match valid (non-nil) expressions. + if _, ok := val.Interface().(ast.Expr); ok && !val.IsNil() { + if old, ok := m[name]; ok { + return match(nil, old, val) + } + m[name] = val + return true + } + } + } + + // Otherwise, pattern and val must match recursively. + if !pattern.IsValid() || !val.IsValid() { + return !pattern.IsValid() && !val.IsValid() + } + if pattern.Type() != val.Type() { + return false + } + + // Special cases. + switch pattern.Type() { + case identType: + // For identifiers, only the names need to match + // (and none of the other *ast.Object information). + // This is a common case, handle it all here instead + // of recursing down any further via reflection. + p := pattern.Interface().(*ast.Ident) + v := val.Interface().(*ast.Ident) + return p == nil && v == nil || p != nil && v != nil && p.Name == v.Name + case objectPtrType, positionType: + // object pointers and token positions always match + return true + case callExprType: + // For calls, the Ellipsis fields (token.Position) must + // match since that is how f(x) and f(x...) are different. + // Check them here but fall through for the remaining fields. + p := pattern.Interface().(*ast.CallExpr) + v := val.Interface().(*ast.CallExpr) + if p.Ellipsis.IsValid() != v.Ellipsis.IsValid() { + return false + } + } + + p := reflect.Indirect(pattern) + v := reflect.Indirect(val) + if !p.IsValid() || !v.IsValid() { + return !p.IsValid() && !v.IsValid() + } + + switch p.Kind() { + case reflect.Slice: + if p.Len() != v.Len() { + return false + } + for i := 0; i < p.Len(); i++ { + if !match(m, p.Index(i), v.Index(i)) { + return false + } + } + return true + + case reflect.Struct: + for i := 0; i < p.NumField(); i++ { + if !match(m, p.Field(i), v.Field(i)) { + return false + } + } + return true + + case reflect.Interface: + return match(m, p.Elem(), v.Elem()) + } + + // Handle token integers, etc. + return p.Interface() == v.Interface() +} + +// subst returns a copy of pattern with values from m substituted in place +// of wildcards and pos used as the position of tokens from the pattern. +// if m == nil, subst returns a copy of pattern and doesn't change the line +// number information. +func subst(m map[string]reflect.Value, pattern reflect.Value, pos reflect.Value) reflect.Value { + if !pattern.IsValid() { + return reflect.Value{} + } + + // Wildcard gets replaced with map value. + if m != nil && pattern.Type() == identType { + name := pattern.Interface().(*ast.Ident).Name + if isWildcard(name) { + if old, ok := m[name]; ok { + return subst(nil, old, reflect.Value{}) + } + } + } + + if pos.IsValid() && pattern.Type() == positionType { + // use new position only if old position was valid in the first place + if old := pattern.Interface().(token.Pos); !old.IsValid() { + return pattern + } + return pos + } + + // Otherwise copy. + switch p := pattern; p.Kind() { + case reflect.Slice: + v := reflect.MakeSlice(p.Type(), p.Len(), p.Len()) + for i := 0; i < p.Len(); i++ { + v.Index(i).Set(subst(m, p.Index(i), pos)) + } + return v + + case reflect.Struct: + v := reflect.New(p.Type()).Elem() + for i := 0; i < p.NumField(); i++ { + v.Field(i).Set(subst(m, p.Field(i), pos)) + } + return v + + case reflect.Ptr: + v := reflect.New(p.Type()).Elem() + if elem := p.Elem(); elem.IsValid() { + v.Set(subst(m, elem, pos).Addr()) + } + return v + + case reflect.Interface: + v := reflect.New(p.Type()).Elem() + if elem := p.Elem(); elem.IsValid() { + v.Set(subst(m, elem, pos)) + } + return v + } + + return pattern +} diff --git a/vendor/github.com/klauspost/cpuid/private/cpuid.go b/vendor/github.com/klauspost/cpuid/private/cpuid.go new file mode 100644 index 000000000..21712142c --- /dev/null +++ b/vendor/github.com/klauspost/cpuid/private/cpuid.go @@ -0,0 +1,1024 @@ +// Generated, DO NOT EDIT, +// but copy it to your own project and rename the package. +// See more at http://github.com/klauspost/cpuid + +package cpuid + +import "strings" + +// Vendor is a representation of a CPU vendor. +type vendor int + +const ( + other vendor = iota + intel + amd + via + transmeta + nsc + kvm // Kernel-based Virtual Machine + msvm // Microsoft Hyper-V or Windows Virtual PC + vmware + xenhvm +) + +const ( + cmov = 1 << iota // i686 CMOV + nx // NX (No-Execute) bit + amd3dnow // AMD 3DNOW + amd3dnowext // AMD 3DNowExt + mmx // standard MMX + mmxext // SSE integer functions or AMD MMX ext + sse // SSE functions + sse2 // P4 SSE functions + sse3 // Prescott SSE3 functions + ssse3 // Conroe SSSE3 functions + sse4 // Penryn SSE4.1 functions + sse4a // AMD Barcelona microarchitecture SSE4a instructions + sse42 // Nehalem SSE4.2 functions + avx // AVX functions + avx2 // AVX2 functions + fma3 // Intel FMA 3 + fma4 // Bulldozer FMA4 functions + xop // Bulldozer XOP functions + f16c // Half-precision floating-point conversion + bmi1 // Bit Manipulation Instruction Set 1 + bmi2 // Bit Manipulation Instruction Set 2 + tbm // AMD Trailing Bit Manipulation + lzcnt // LZCNT instruction + popcnt // POPCNT instruction + aesni // Advanced Encryption Standard New Instructions + clmul // Carry-less Multiplication + htt // Hyperthreading (enabled) + hle // Hardware Lock Elision + rtm // Restricted Transactional Memory + rdrand // RDRAND instruction is available + rdseed // RDSEED instruction is available + adx // Intel ADX (Multi-Precision Add-Carry Instruction Extensions) + sha // Intel SHA Extensions + avx512f // AVX-512 Foundation + avx512dq // AVX-512 Doubleword and Quadword Instructions + avx512ifma // AVX-512 Integer Fused Multiply-Add Instructions + avx512pf // AVX-512 Prefetch Instructions + avx512er // AVX-512 Exponential and Reciprocal Instructions + avx512cd // AVX-512 Conflict Detection Instructions + avx512bw // AVX-512 Byte and Word Instructions + avx512vl // AVX-512 Vector Length Extensions + avx512vbmi // AVX-512 Vector Bit Manipulation Instructions + mpx // Intel MPX (Memory Protection Extensions) + erms // Enhanced REP MOVSB/STOSB + rdtscp // RDTSCP Instruction + cx16 // CMPXCHG16B Instruction + sgx // Software Guard Extensions + + // Performance indicators + sse2slow // SSE2 is supported, but usually not faster + sse3slow // SSE3 is supported, but usually not faster + atom // Atom processor, some SSSE3 instructions are slower +) + +var flagNames = map[flags]string{ + cmov: "CMOV", // i686 CMOV + nx: "NX", // NX (No-Execute) bit + amd3dnow: "AMD3DNOW", // AMD 3DNOW + amd3dnowext: "AMD3DNOWEXT", // AMD 3DNowExt + mmx: "MMX", // Standard MMX + mmxext: "MMXEXT", // SSE integer functions or AMD MMX ext + sse: "SSE", // SSE functions + sse2: "SSE2", // P4 SSE2 functions + sse3: "SSE3", // Prescott SSE3 functions + ssse3: "SSSE3", // Conroe SSSE3 functions + sse4: "SSE4.1", // Penryn SSE4.1 functions + sse4a: "SSE4A", // AMD Barcelona microarchitecture SSE4a instructions + sse42: "SSE4.2", // Nehalem SSE4.2 functions + avx: "AVX", // AVX functions + avx2: "AVX2", // AVX functions + fma3: "FMA3", // Intel FMA 3 + fma4: "FMA4", // Bulldozer FMA4 functions + xop: "XOP", // Bulldozer XOP functions + f16c: "F16C", // Half-precision floating-point conversion + bmi1: "BMI1", // Bit Manipulation Instruction Set 1 + bmi2: "BMI2", // Bit Manipulation Instruction Set 2 + tbm: "TBM", // AMD Trailing Bit Manipulation + lzcnt: "LZCNT", // LZCNT instruction + popcnt: "POPCNT", // POPCNT instruction + aesni: "AESNI", // Advanced Encryption Standard New Instructions + clmul: "CLMUL", // Carry-less Multiplication + htt: "HTT", // Hyperthreading (enabled) + hle: "HLE", // Hardware Lock Elision + rtm: "RTM", // Restricted Transactional Memory + rdrand: "RDRAND", // RDRAND instruction is available + rdseed: "RDSEED", // RDSEED instruction is available + adx: "ADX", // Intel ADX (Multi-Precision Add-Carry Instruction Extensions) + sha: "SHA", // Intel SHA Extensions + avx512f: "AVX512F", // AVX-512 Foundation + avx512dq: "AVX512DQ", // AVX-512 Doubleword and Quadword Instructions + avx512ifma: "AVX512IFMA", // AVX-512 Integer Fused Multiply-Add Instructions + avx512pf: "AVX512PF", // AVX-512 Prefetch Instructions + avx512er: "AVX512ER", // AVX-512 Exponential and Reciprocal Instructions + avx512cd: "AVX512CD", // AVX-512 Conflict Detection Instructions + avx512bw: "AVX512BW", // AVX-512 Byte and Word Instructions + avx512vl: "AVX512VL", // AVX-512 Vector Length Extensions + avx512vbmi: "AVX512VBMI", // AVX-512 Vector Bit Manipulation Instructions + mpx: "MPX", // Intel MPX (Memory Protection Extensions) + erms: "ERMS", // Enhanced REP MOVSB/STOSB + rdtscp: "RDTSCP", // RDTSCP Instruction + cx16: "CX16", // CMPXCHG16B Instruction + sgx: "SGX", // Software Guard Extensions + + // Performance indicators + sse2slow: "SSE2SLOW", // SSE2 supported, but usually not faster + sse3slow: "SSE3SLOW", // SSE3 supported, but usually not faster + atom: "ATOM", // Atom processor, some SSSE3 instructions are slower + +} + +// CPUInfo contains information about the detected system CPU. +type cpuInfo struct { + brandname string // Brand name reported by the CPU + vendorid vendor // Comparable CPU vendor ID + features flags // Features of the CPU + physicalcores int // Number of physical processor cores in your CPU. Will be 0 if undetectable. + threadspercore int // Number of threads per physical core. Will be 1 if undetectable. + logicalcores int // Number of physical cores times threads that can run on each core through the use of hyperthreading. Will be 0 if undetectable. + family int // CPU family number + model int // CPU model number + cacheline int // Cache line size in bytes. Will be 0 if undetectable. + cache struct { + l1i int // L1 Instruction Cache (per core or shared). Will be -1 if undetected + l1d int // L1 Data Cache (per core or shared). Will be -1 if undetected + l2 int // L2 Cache (per core or shared). Will be -1 if undetected + l3 int // L3 Instruction Cache (per core or shared). Will be -1 if undetected + } + sgx sgxsupport + maxFunc uint32 + maxExFunc uint32 +} + +var cpuid func(op uint32) (eax, ebx, ecx, edx uint32) +var cpuidex func(op, op2 uint32) (eax, ebx, ecx, edx uint32) +var xgetbv func(index uint32) (eax, edx uint32) +var rdtscpAsm func() (eax, ebx, ecx, edx uint32) + +// CPU contains information about the CPU as detected on startup, +// or when Detect last was called. +// +// Use this as the primary entry point to you data, +// this way queries are +var cpu cpuInfo + +func init() { + initCPU() + detect() +} + +// Detect will re-detect current CPU info. +// This will replace the content of the exported CPU variable. +// +// Unless you expect the CPU to change while you are running your program +// you should not need to call this function. +// If you call this, you must ensure that no other goroutine is accessing the +// exported CPU variable. +func detect() { + cpu.maxFunc = maxFunctionID() + cpu.maxExFunc = maxExtendedFunction() + cpu.brandname = brandName() + cpu.cacheline = cacheLine() + cpu.family, cpu.model = familyModel() + cpu.features = support() + cpu.sgx = hasSGX(cpu.features&sgx != 0) + cpu.threadspercore = threadsPerCore() + cpu.logicalcores = logicalCores() + cpu.physicalcores = physicalCores() + cpu.vendorid = vendorID() + cpu.cacheSize() +} + +// Generated here: http://play.golang.org/p/BxFH2Gdc0G + +// Cmov indicates support of CMOV instructions +func (c cpuInfo) cmov() bool { + return c.features&cmov != 0 +} + +// Amd3dnow indicates support of AMD 3DNOW! instructions +func (c cpuInfo) amd3dnow() bool { + return c.features&amd3dnow != 0 +} + +// Amd3dnowExt indicates support of AMD 3DNOW! Extended instructions +func (c cpuInfo) amd3dnowext() bool { + return c.features&amd3dnowext != 0 +} + +// MMX indicates support of MMX instructions +func (c cpuInfo) mmx() bool { + return c.features&mmx != 0 +} + +// MMXExt indicates support of MMXEXT instructions +// (SSE integer functions or AMD MMX ext) +func (c cpuInfo) mmxext() bool { + return c.features&mmxext != 0 +} + +// SSE indicates support of SSE instructions +func (c cpuInfo) sse() bool { + return c.features&sse != 0 +} + +// SSE2 indicates support of SSE 2 instructions +func (c cpuInfo) sse2() bool { + return c.features&sse2 != 0 +} + +// SSE3 indicates support of SSE 3 instructions +func (c cpuInfo) sse3() bool { + return c.features&sse3 != 0 +} + +// SSSE3 indicates support of SSSE 3 instructions +func (c cpuInfo) ssse3() bool { + return c.features&ssse3 != 0 +} + +// SSE4 indicates support of SSE 4 (also called SSE 4.1) instructions +func (c cpuInfo) sse4() bool { + return c.features&sse4 != 0 +} + +// SSE42 indicates support of SSE4.2 instructions +func (c cpuInfo) sse42() bool { + return c.features&sse42 != 0 +} + +// AVX indicates support of AVX instructions +// and operating system support of AVX instructions +func (c cpuInfo) avx() bool { + return c.features&avx != 0 +} + +// AVX2 indicates support of AVX2 instructions +func (c cpuInfo) avx2() bool { + return c.features&avx2 != 0 +} + +// FMA3 indicates support of FMA3 instructions +func (c cpuInfo) fma3() bool { + return c.features&fma3 != 0 +} + +// FMA4 indicates support of FMA4 instructions +func (c cpuInfo) fma4() bool { + return c.features&fma4 != 0 +} + +// XOP indicates support of XOP instructions +func (c cpuInfo) xop() bool { + return c.features&xop != 0 +} + +// F16C indicates support of F16C instructions +func (c cpuInfo) f16c() bool { + return c.features&f16c != 0 +} + +// BMI1 indicates support of BMI1 instructions +func (c cpuInfo) bmi1() bool { + return c.features&bmi1 != 0 +} + +// BMI2 indicates support of BMI2 instructions +func (c cpuInfo) bmi2() bool { + return c.features&bmi2 != 0 +} + +// TBM indicates support of TBM instructions +// (AMD Trailing Bit Manipulation) +func (c cpuInfo) tbm() bool { + return c.features&tbm != 0 +} + +// Lzcnt indicates support of LZCNT instruction +func (c cpuInfo) lzcnt() bool { + return c.features&lzcnt != 0 +} + +// Popcnt indicates support of POPCNT instruction +func (c cpuInfo) popcnt() bool { + return c.features&popcnt != 0 +} + +// HTT indicates the processor has Hyperthreading enabled +func (c cpuInfo) htt() bool { + return c.features&htt != 0 +} + +// SSE2Slow indicates that SSE2 may be slow on this processor +func (c cpuInfo) sse2slow() bool { + return c.features&sse2slow != 0 +} + +// SSE3Slow indicates that SSE3 may be slow on this processor +func (c cpuInfo) sse3slow() bool { + return c.features&sse3slow != 0 +} + +// AesNi indicates support of AES-NI instructions +// (Advanced Encryption Standard New Instructions) +func (c cpuInfo) aesni() bool { + return c.features&aesni != 0 +} + +// Clmul indicates support of CLMUL instructions +// (Carry-less Multiplication) +func (c cpuInfo) clmul() bool { + return c.features&clmul != 0 +} + +// NX indicates support of NX (No-Execute) bit +func (c cpuInfo) nx() bool { + return c.features&nx != 0 +} + +// SSE4A indicates support of AMD Barcelona microarchitecture SSE4a instructions +func (c cpuInfo) sse4a() bool { + return c.features&sse4a != 0 +} + +// HLE indicates support of Hardware Lock Elision +func (c cpuInfo) hle() bool { + return c.features&hle != 0 +} + +// RTM indicates support of Restricted Transactional Memory +func (c cpuInfo) rtm() bool { + return c.features&rtm != 0 +} + +// Rdrand indicates support of RDRAND instruction is available +func (c cpuInfo) rdrand() bool { + return c.features&rdrand != 0 +} + +// Rdseed indicates support of RDSEED instruction is available +func (c cpuInfo) rdseed() bool { + return c.features&rdseed != 0 +} + +// ADX indicates support of Intel ADX (Multi-Precision Add-Carry Instruction Extensions) +func (c cpuInfo) adx() bool { + return c.features&adx != 0 +} + +// SHA indicates support of Intel SHA Extensions +func (c cpuInfo) sha() bool { + return c.features&sha != 0 +} + +// AVX512F indicates support of AVX-512 Foundation +func (c cpuInfo) avx512f() bool { + return c.features&avx512f != 0 +} + +// AVX512DQ indicates support of AVX-512 Doubleword and Quadword Instructions +func (c cpuInfo) avx512dq() bool { + return c.features&avx512dq != 0 +} + +// AVX512IFMA indicates support of AVX-512 Integer Fused Multiply-Add Instructions +func (c cpuInfo) avx512ifma() bool { + return c.features&avx512ifma != 0 +} + +// AVX512PF indicates support of AVX-512 Prefetch Instructions +func (c cpuInfo) avx512pf() bool { + return c.features&avx512pf != 0 +} + +// AVX512ER indicates support of AVX-512 Exponential and Reciprocal Instructions +func (c cpuInfo) avx512er() bool { + return c.features&avx512er != 0 +} + +// AVX512CD indicates support of AVX-512 Conflict Detection Instructions +func (c cpuInfo) avx512cd() bool { + return c.features&avx512cd != 0 +} + +// AVX512BW indicates support of AVX-512 Byte and Word Instructions +func (c cpuInfo) avx512bw() bool { + return c.features&avx512bw != 0 +} + +// AVX512VL indicates support of AVX-512 Vector Length Extensions +func (c cpuInfo) avx512vl() bool { + return c.features&avx512vl != 0 +} + +// AVX512VBMI indicates support of AVX-512 Vector Bit Manipulation Instructions +func (c cpuInfo) avx512vbmi() bool { + return c.features&avx512vbmi != 0 +} + +// MPX indicates support of Intel MPX (Memory Protection Extensions) +func (c cpuInfo) mpx() bool { + return c.features&mpx != 0 +} + +// ERMS indicates support of Enhanced REP MOVSB/STOSB +func (c cpuInfo) erms() bool { + return c.features&erms != 0 +} + +// RDTSCP Instruction is available. +func (c cpuInfo) rdtscp() bool { + return c.features&rdtscp != 0 +} + +// CX16 indicates if CMPXCHG16B instruction is available. +func (c cpuInfo) cx16() bool { + return c.features&cx16 != 0 +} + +// TSX is split into HLE (Hardware Lock Elision) and RTM (Restricted Transactional Memory) detection. +// So TSX simply checks that. +func (c cpuInfo) tsx() bool { + return c.features&(mpx|rtm) == mpx|rtm +} + +// Atom indicates an Atom processor +func (c cpuInfo) atom() bool { + return c.features&atom != 0 +} + +// Intel returns true if vendor is recognized as Intel +func (c cpuInfo) intel() bool { + return c.vendorid == intel +} + +// AMD returns true if vendor is recognized as AMD +func (c cpuInfo) amd() bool { + return c.vendorid == amd +} + +// Transmeta returns true if vendor is recognized as Transmeta +func (c cpuInfo) transmeta() bool { + return c.vendorid == transmeta +} + +// NSC returns true if vendor is recognized as National Semiconductor +func (c cpuInfo) nsc() bool { + return c.vendorid == nsc +} + +// VIA returns true if vendor is recognized as VIA +func (c cpuInfo) via() bool { + return c.vendorid == via +} + +// RTCounter returns the 64-bit time-stamp counter +// Uses the RDTSCP instruction. The value 0 is returned +// if the CPU does not support the instruction. +func (c cpuInfo) rtcounter() uint64 { + if !c.rdtscp() { + return 0 + } + a, _, _, d := rdtscpAsm() + return uint64(a) | (uint64(d) << 32) +} + +// Ia32TscAux returns the IA32_TSC_AUX part of the RDTSCP. +// This variable is OS dependent, but on Linux contains information +// about the current cpu/core the code is running on. +// If the RDTSCP instruction isn't supported on the CPU, the value 0 is returned. +func (c cpuInfo) ia32tscaux() uint32 { + if !c.rdtscp() { + return 0 + } + _, _, ecx, _ := rdtscpAsm() + return ecx +} + +// LogicalCPU will return the Logical CPU the code is currently executing on. +// This is likely to change when the OS re-schedules the running thread +// to another CPU. +// If the current core cannot be detected, -1 will be returned. +func (c cpuInfo) logicalcpu() int { + if c.maxFunc < 1 { + return -1 + } + _, ebx, _, _ := cpuid(1) + return int(ebx >> 24) +} + +// VM Will return true if the cpu id indicates we are in +// a virtual machine. This is only a hint, and will very likely +// have many false negatives. +func (c cpuInfo) vm() bool { + switch c.vendorid { + case msvm, kvm, vmware, xenhvm: + return true + } + return false +} + +// Flags contains detected cpu features and caracteristics +type flags uint64 + +// String returns a string representation of the detected +// CPU features. +func (f flags) String() string { + return strings.Join(f.strings(), ",") +} + +// Strings returns and array of the detected features. +func (f flags) strings() []string { + s := support() + r := make([]string, 0, 20) + for i := uint(0); i < 64; i++ { + key := flags(1 << i) + val := flagNames[key] + if s&key != 0 { + r = append(r, val) + } + } + return r +} + +func maxExtendedFunction() uint32 { + eax, _, _, _ := cpuid(0x80000000) + return eax +} + +func maxFunctionID() uint32 { + a, _, _, _ := cpuid(0) + return a +} + +func brandName() string { + if maxExtendedFunction() >= 0x80000004 { + v := make([]uint32, 0, 48) + for i := uint32(0); i < 3; i++ { + a, b, c, d := cpuid(0x80000002 + i) + v = append(v, a, b, c, d) + } + return strings.Trim(string(valAsString(v...)), " ") + } + return "unknown" +} + +func threadsPerCore() int { + mfi := maxFunctionID() + if mfi < 0x4 || vendorID() != intel { + return 1 + } + + if mfi < 0xb { + _, b, _, d := cpuid(1) + if (d & (1 << 28)) != 0 { + // v will contain logical core count + v := (b >> 16) & 255 + if v > 1 { + a4, _, _, _ := cpuid(4) + // physical cores + v2 := (a4 >> 26) + 1 + if v2 > 0 { + return int(v) / int(v2) + } + } + } + return 1 + } + _, b, _, _ := cpuidex(0xb, 0) + if b&0xffff == 0 { + return 1 + } + return int(b & 0xffff) +} + +func logicalCores() int { + mfi := maxFunctionID() + switch vendorID() { + case intel: + // Use this on old Intel processors + if mfi < 0xb { + if mfi < 1 { + return 0 + } + // CPUID.1:EBX[23:16] represents the maximum number of addressable IDs (initial APIC ID) + // that can be assigned to logical processors in a physical package. + // The value may not be the same as the number of logical processors that are present in the hardware of a physical package. + _, ebx, _, _ := cpuid(1) + logical := (ebx >> 16) & 0xff + return int(logical) + } + _, b, _, _ := cpuidex(0xb, 1) + return int(b & 0xffff) + case amd: + _, b, _, _ := cpuid(1) + return int((b >> 16) & 0xff) + default: + return 0 + } +} + +func familyModel() (int, int) { + if maxFunctionID() < 0x1 { + return 0, 0 + } + eax, _, _, _ := cpuid(1) + family := ((eax >> 8) & 0xf) + ((eax >> 20) & 0xff) + model := ((eax >> 4) & 0xf) + ((eax >> 12) & 0xf0) + return int(family), int(model) +} + +func physicalCores() int { + switch vendorID() { + case intel: + return logicalCores() / threadsPerCore() + case amd: + if maxExtendedFunction() >= 0x80000008 { + _, _, c, _ := cpuid(0x80000008) + return int(c&0xff) + 1 + } + } + return 0 +} + +// Except from http://en.wikipedia.org/wiki/CPUID#EAX.3D0:_Get_vendor_ID +var vendorMapping = map[string]vendor{ + "AMDisbetter!": amd, + "AuthenticAMD": amd, + "CentaurHauls": via, + "GenuineIntel": intel, + "TransmetaCPU": transmeta, + "GenuineTMx86": transmeta, + "Geode by NSC": nsc, + "VIA VIA VIA ": via, + "KVMKVMKVMKVM": kvm, + "Microsoft Hv": msvm, + "VMwareVMware": vmware, + "XenVMMXenVMM": xenhvm, +} + +func vendorID() vendor { + _, b, c, d := cpuid(0) + v := valAsString(b, d, c) + vend, ok := vendorMapping[string(v)] + if !ok { + return other + } + return vend +} + +func cacheLine() int { + if maxFunctionID() < 0x1 { + return 0 + } + + _, ebx, _, _ := cpuid(1) + cache := (ebx & 0xff00) >> 5 // cflush size + if cache == 0 && maxExtendedFunction() >= 0x80000006 { + _, _, ecx, _ := cpuid(0x80000006) + cache = ecx & 0xff // cacheline size + } + // TODO: Read from Cache and TLB Information + return int(cache) +} + +func (c *cpuInfo) cacheSize() { + c.cache.l1d = -1 + c.cache.l1i = -1 + c.cache.l2 = -1 + c.cache.l3 = -1 + vendor := vendorID() + switch vendor { + case intel: + if maxFunctionID() < 4 { + return + } + for i := uint32(0); ; i++ { + eax, ebx, ecx, _ := cpuidex(4, i) + cacheType := eax & 15 + if cacheType == 0 { + break + } + cacheLevel := (eax >> 5) & 7 + coherency := int(ebx&0xfff) + 1 + partitions := int((ebx>>12)&0x3ff) + 1 + associativity := int((ebx>>22)&0x3ff) + 1 + sets := int(ecx) + 1 + size := associativity * partitions * coherency * sets + switch cacheLevel { + case 1: + if cacheType == 1 { + // 1 = Data Cache + c.cache.l1d = size + } else if cacheType == 2 { + // 2 = Instruction Cache + c.cache.l1i = size + } else { + if c.cache.l1d < 0 { + c.cache.l1i = size + } + if c.cache.l1i < 0 { + c.cache.l1i = size + } + } + case 2: + c.cache.l2 = size + case 3: + c.cache.l3 = size + } + } + case amd: + // Untested. + if maxExtendedFunction() < 0x80000005 { + return + } + _, _, ecx, edx := cpuid(0x80000005) + c.cache.l1d = int(((ecx >> 24) & 0xFF) * 1024) + c.cache.l1i = int(((edx >> 24) & 0xFF) * 1024) + + if maxExtendedFunction() < 0x80000006 { + return + } + _, _, ecx, _ = cpuid(0x80000006) + c.cache.l2 = int(((ecx >> 16) & 0xFFFF) * 1024) + } + + return +} + +type sgxsupport struct { + available bool + sgx1supported bool + sgx2supported bool + maxenclavesizenot64 int64 + maxenclavesize64 int64 +} + +func hasSGX(available bool) (rval sgxsupport) { + rval.available = available + + if !available { + return + } + + a, _, _, d := cpuidex(0x12, 0) + rval.sgx1supported = a&0x01 != 0 + rval.sgx2supported = a&0x02 != 0 + rval.maxenclavesizenot64 = 1 << (d & 0xFF) // pow 2 + rval.maxenclavesize64 = 1 << ((d >> 8) & 0xFF) // pow 2 + + return +} + +func support() flags { + mfi := maxFunctionID() + vend := vendorID() + if mfi < 0x1 { + return 0 + } + rval := uint64(0) + _, _, c, d := cpuid(1) + if (d & (1 << 15)) != 0 { + rval |= cmov + } + if (d & (1 << 23)) != 0 { + rval |= mmx + } + if (d & (1 << 25)) != 0 { + rval |= mmxext + } + if (d & (1 << 25)) != 0 { + rval |= sse + } + if (d & (1 << 26)) != 0 { + rval |= sse2 + } + if (c & 1) != 0 { + rval |= sse3 + } + if (c & 0x00000200) != 0 { + rval |= ssse3 + } + if (c & 0x00080000) != 0 { + rval |= sse4 + } + if (c & 0x00100000) != 0 { + rval |= sse42 + } + if (c & (1 << 25)) != 0 { + rval |= aesni + } + if (c & (1 << 1)) != 0 { + rval |= clmul + } + if c&(1<<23) != 0 { + rval |= popcnt + } + if c&(1<<30) != 0 { + rval |= rdrand + } + if c&(1<<29) != 0 { + rval |= f16c + } + if c&(1<<13) != 0 { + rval |= cx16 + } + if vend == intel && (d&(1<<28)) != 0 && mfi >= 4 { + if threadsPerCore() > 1 { + rval |= htt + } + } + + // Check XGETBV, OXSAVE and AVX bits + if c&(1<<26) != 0 && c&(1<<27) != 0 && c&(1<<28) != 0 { + // Check for OS support + eax, _ := xgetbv(0) + if (eax & 0x6) == 0x6 { + rval |= avx + if (c & 0x00001000) != 0 { + rval |= fma3 + } + } + } + + // Check AVX2, AVX2 requires OS support, but BMI1/2 don't. + if mfi >= 7 { + _, ebx, ecx, _ := cpuidex(7, 0) + if (rval&avx) != 0 && (ebx&0x00000020) != 0 { + rval |= avx2 + } + if (ebx & 0x00000008) != 0 { + rval |= bmi1 + if (ebx & 0x00000100) != 0 { + rval |= bmi2 + } + } + if ebx&(1<<2) != 0 { + rval |= sgx + } + if ebx&(1<<4) != 0 { + rval |= hle + } + if ebx&(1<<9) != 0 { + rval |= erms + } + if ebx&(1<<11) != 0 { + rval |= rtm + } + if ebx&(1<<14) != 0 { + rval |= mpx + } + if ebx&(1<<18) != 0 { + rval |= rdseed + } + if ebx&(1<<19) != 0 { + rval |= adx + } + if ebx&(1<<29) != 0 { + rval |= sha + } + + // Only detect AVX-512 features if XGETBV is supported + if c&((1<<26)|(1<<27)) == (1<<26)|(1<<27) { + // Check for OS support + eax, _ := xgetbv(0) + + // Verify that XCR0[7:5] = ‘111b’ (OPMASK state, upper 256-bit of ZMM0-ZMM15 and + // ZMM16-ZMM31 state are enabled by OS) + /// and that XCR0[2:1] = ‘11b’ (XMM state and YMM state are enabled by OS). + if (eax>>5)&7 == 7 && (eax>>1)&3 == 3 { + if ebx&(1<<16) != 0 { + rval |= avx512f + } + if ebx&(1<<17) != 0 { + rval |= avx512dq + } + if ebx&(1<<21) != 0 { + rval |= avx512ifma + } + if ebx&(1<<26) != 0 { + rval |= avx512pf + } + if ebx&(1<<27) != 0 { + rval |= avx512er + } + if ebx&(1<<28) != 0 { + rval |= avx512cd + } + if ebx&(1<<30) != 0 { + rval |= avx512bw + } + if ebx&(1<<31) != 0 { + rval |= avx512vl + } + // ecx + if ecx&(1<<1) != 0 { + rval |= avx512vbmi + } + } + } + } + + if maxExtendedFunction() >= 0x80000001 { + _, _, c, d := cpuid(0x80000001) + if (c & (1 << 5)) != 0 { + rval |= lzcnt + rval |= popcnt + } + if (d & (1 << 31)) != 0 { + rval |= amd3dnow + } + if (d & (1 << 30)) != 0 { + rval |= amd3dnowext + } + if (d & (1 << 23)) != 0 { + rval |= mmx + } + if (d & (1 << 22)) != 0 { + rval |= mmxext + } + if (c & (1 << 6)) != 0 { + rval |= sse4a + } + if d&(1<<20) != 0 { + rval |= nx + } + if d&(1<<27) != 0 { + rval |= rdtscp + } + + /* Allow for selectively disabling SSE2 functions on AMD processors + with SSE2 support but not SSE4a. This includes Athlon64, some + Opteron, and some Sempron processors. MMX, SSE, or 3DNow! are faster + than SSE2 often enough to utilize this special-case flag. + AV_CPU_FLAG_SSE2 and AV_CPU_FLAG_SSE2SLOW are both set in this case + so that SSE2 is used unless explicitly disabled by checking + AV_CPU_FLAG_SSE2SLOW. */ + if vendorID() != intel && + rval&sse2 != 0 && (c&0x00000040) == 0 { + rval |= sse2slow + } + + /* XOP and FMA4 use the AVX instruction coding scheme, so they can't be + * used unless the OS has AVX support. */ + if (rval & avx) != 0 { + if (c & 0x00000800) != 0 { + rval |= xop + } + if (c & 0x00010000) != 0 { + rval |= fma4 + } + } + + if vendorID() == intel { + family, model := familyModel() + if family == 6 && (model == 9 || model == 13 || model == 14) { + /* 6/9 (pentium-m "banias"), 6/13 (pentium-m "dothan"), and + * 6/14 (core1 "yonah") theoretically support sse2, but it's + * usually slower than mmx. */ + if (rval & sse2) != 0 { + rval |= sse2slow + } + if (rval & sse3) != 0 { + rval |= sse3slow + } + } + /* The Atom processor has SSSE3 support, which is useful in many cases, + * but sometimes the SSSE3 version is slower than the SSE2 equivalent + * on the Atom, but is generally faster on other processors supporting + * SSSE3. This flag allows for selectively disabling certain SSSE3 + * functions on the Atom. */ + if family == 6 && model == 28 { + rval |= atom + } + } + } + return flags(rval) +} + +func valAsString(values ...uint32) []byte { + r := make([]byte, 4*len(values)) + for i, v := range values { + dst := r[i*4:] + dst[0] = byte(v & 0xff) + dst[1] = byte((v >> 8) & 0xff) + dst[2] = byte((v >> 16) & 0xff) + dst[3] = byte((v >> 24) & 0xff) + switch { + case dst[0] == 0: + return r[:i*4] + case dst[1] == 0: + return r[:i*4+1] + case dst[2] == 0: + return r[:i*4+2] + case dst[3] == 0: + return r[:i*4+3] + } + } + return r +} diff --git a/vendor/github.com/klauspost/cpuid/private/cpuid_386.s b/vendor/github.com/klauspost/cpuid/private/cpuid_386.s new file mode 100644 index 000000000..4d731711e --- /dev/null +++ b/vendor/github.com/klauspost/cpuid/private/cpuid_386.s @@ -0,0 +1,42 @@ +// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. + +// +build 386,!gccgo + +// func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) +TEXT ·asmCpuid(SB), 7, $0 + XORL CX, CX + MOVL op+0(FP), AX + CPUID + MOVL AX, eax+4(FP) + MOVL BX, ebx+8(FP) + MOVL CX, ecx+12(FP) + MOVL DX, edx+16(FP) + RET + +// func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32) +TEXT ·asmCpuidex(SB), 7, $0 + MOVL op+0(FP), AX + MOVL op2+4(FP), CX + CPUID + MOVL AX, eax+8(FP) + MOVL BX, ebx+12(FP) + MOVL CX, ecx+16(FP) + MOVL DX, edx+20(FP) + RET + +// func xgetbv(index uint32) (eax, edx uint32) +TEXT ·asmXgetbv(SB), 7, $0 + MOVL index+0(FP), CX + BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV + MOVL AX, eax+4(FP) + MOVL DX, edx+8(FP) + RET + +// func asmRdtscpAsm() (eax, ebx, ecx, edx uint32) +TEXT ·asmRdtscpAsm(SB), 7, $0 + BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP + MOVL AX, eax+0(FP) + MOVL BX, ebx+4(FP) + MOVL CX, ecx+8(FP) + MOVL DX, edx+12(FP) + RET diff --git a/vendor/github.com/klauspost/cpuid/private/cpuid_amd64.s b/vendor/github.com/klauspost/cpuid/private/cpuid_amd64.s new file mode 100644 index 000000000..3c1d60e42 --- /dev/null +++ b/vendor/github.com/klauspost/cpuid/private/cpuid_amd64.s @@ -0,0 +1,42 @@ +// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. + +//+build amd64,!gccgo + +// func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) +TEXT ·asmCpuid(SB), 7, $0 + XORQ CX, CX + MOVL op+0(FP), AX + CPUID + MOVL AX, eax+8(FP) + MOVL BX, ebx+12(FP) + MOVL CX, ecx+16(FP) + MOVL DX, edx+20(FP) + RET + +// func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32) +TEXT ·asmCpuidex(SB), 7, $0 + MOVL op+0(FP), AX + MOVL op2+4(FP), CX + CPUID + MOVL AX, eax+8(FP) + MOVL BX, ebx+12(FP) + MOVL CX, ecx+16(FP) + MOVL DX, edx+20(FP) + RET + +// func asmXgetbv(index uint32) (eax, edx uint32) +TEXT ·asmXgetbv(SB), 7, $0 + MOVL index+0(FP), CX + BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV + MOVL AX, eax+8(FP) + MOVL DX, edx+12(FP) + RET + +// func asmRdtscpAsm() (eax, ebx, ecx, edx uint32) +TEXT ·asmRdtscpAsm(SB), 7, $0 + BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP + MOVL AX, eax+0(FP) + MOVL BX, ebx+4(FP) + MOVL CX, ecx+8(FP) + MOVL DX, edx+12(FP) + RET diff --git a/vendor/github.com/klauspost/cpuid/private/cpuid_detect_intel.go b/vendor/github.com/klauspost/cpuid/private/cpuid_detect_intel.go new file mode 100644 index 000000000..a5f04dd6d --- /dev/null +++ b/vendor/github.com/klauspost/cpuid/private/cpuid_detect_intel.go @@ -0,0 +1,17 @@ +// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. + +// +build 386,!gccgo amd64,!gccgo + +package cpuid + +func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) +func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32) +func asmXgetbv(index uint32) (eax, edx uint32) +func asmRdtscpAsm() (eax, ebx, ecx, edx uint32) + +func initCPU() { + cpuid = asmCpuid + cpuidex = asmCpuidex + xgetbv = asmXgetbv + rdtscpAsm = asmRdtscpAsm +} diff --git a/vendor/github.com/klauspost/cpuid/private/cpuid_detect_ref.go b/vendor/github.com/klauspost/cpuid/private/cpuid_detect_ref.go new file mode 100644 index 000000000..909c5d9a7 --- /dev/null +++ b/vendor/github.com/klauspost/cpuid/private/cpuid_detect_ref.go @@ -0,0 +1,23 @@ +// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. + +// +build !amd64,!386 gccgo + +package cpuid + +func initCPU() { + cpuid = func(op uint32) (eax, ebx, ecx, edx uint32) { + return 0, 0, 0, 0 + } + + cpuidex = func(op, op2 uint32) (eax, ebx, ecx, edx uint32) { + return 0, 0, 0, 0 + } + + xgetbv = func(index uint32) (eax, edx uint32) { + return 0, 0 + } + + rdtscpAsm = func() (eax, ebx, ecx, edx uint32) { + return 0, 0, 0, 0 + } +} diff --git a/vendor/manifest b/vendor/manifest index ca99f1172..91a401c7d 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -88,7 +88,7 @@ "importpath": "github.com/google/uuid", "repository": "https://github.com/google/uuid", "vcs": "git", - "revision": "7e072fc3a7be179aee6d3359e46015aa8c995314", + "revision": "dec09d789f3dba190787f8b4454c7d3c936fed9e", "branch": "master", "notests": true }, @@ -125,6 +125,14 @@ "path": "/basic", "notests": true }, + { + "importpath": "github.com/klauspost/cpuid", + "repository": "https://github.com/klauspost/cpuid", + "vcs": "git", + "revision": "ae832f27941af41db13bd6d8efd2493e3b22415a", + "branch": "master", + "notests": true + }, { "importpath": "github.com/lucas-clemente/aes12", "repository": "https://github.com/lucas-clemente/aes12",