diff --git a/cmd/admin-handlers.go b/cmd/admin-handlers.go index 443d4743d..688ce9fae 100644 --- a/cmd/admin-handlers.go +++ b/cmd/admin-handlers.go @@ -1787,3 +1787,42 @@ func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Req } writeSuccessResponseJSON(w, resp) } + +// ServerHardwareInfoHandler - GET /minio/admin/v1/hardwareinfo?Type={hwType} +// ---------- +// Get all hardware information based on input type +// Supported types = cpu +func (a adminAPIHandlers) ServerHardwareInfoHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "HardwareInfo") + + objectAPI := validateAdminReq(ctx, w, r) + if objectAPI == nil { + return + } + + vars := mux.Vars(r) + hardware := vars[madmin.HARDWARE] + + switch madmin.HardwareType(hardware) { + case madmin.CPU: + // Get CPU hardware details from local server's cpu(s) + cpu := getLocalCPUInfo(globalEndpoints, r) + // Notify all other MinIO peers to report cpu hardware + cpus := globalNotificationSys.CPUInfo() + cpus = append(cpus, cpu) + + // Marshal API response + jsonBytes, err := json.Marshal(cpus) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + + // Reply with cpu hardware information (across nodes in a + // distributed setup) as json. + writeSuccessResponseJSON(w, jsonBytes) + + default: + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL) + } +} diff --git a/cmd/admin-router.go b/cmd/admin-router.go index aee2dd9cd..cb870f733 100644 --- a/cmd/admin-router.go +++ b/cmd/admin-router.go @@ -49,6 +49,8 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool) // Info operations adminV1Router.Methods(http.MethodGet).Path("/info").HandlerFunc(httpTraceAll(adminAPI.ServerInfoHandler)) + // Harware Info operations + adminV1Router.Methods(http.MethodGet).Path("/hardware").HandlerFunc(httpTraceAll(adminAPI.ServerHardwareInfoHandler)).Queries("hwType", "{hwType:.*}") if globalIsDistXL || globalIsXL { /// Heal operations diff --git a/cmd/admin-server-info.go b/cmd/admin-server-info.go index f5a67f4ba..2dafe0766 100644 --- a/cmd/admin-server-info.go +++ b/cmd/admin-server-info.go @@ -25,6 +25,8 @@ import ( "github.com/minio/minio/pkg/disk" "github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/mem" + + cpuhw "github.com/shirou/gopsutil/cpu" ) // getLocalMemUsage - returns ServerMemUsageInfo for only the @@ -112,3 +114,34 @@ func getLocalDrivesPerf(endpoints EndpointList, size int64, r *http.Request) mad Size: size, } } + +// getLocalCPUInfo - returns ServerCPUHardwareInfo only for the +// local endpoints from given list of endpoints +func getLocalCPUInfo(endpoints EndpointList, r *http.Request) madmin.ServerCPUHardwareInfo { + var cpuHardwares []cpuhw.InfoStat + seenHosts := set.NewStringSet() + for _, endpoint := range endpoints { + if seenHosts.Contains(endpoint.Host) { + continue + } + // Only proceed for local endpoints + if endpoint.IsLocal { + cpuHardware, err := cpuhw.Info() + if err != nil { + return madmin.ServerCPUHardwareInfo{ + Error: err.Error(), + } + } + cpuHardwares = append(cpuHardwares, cpuHardware...) + } + } + addr := r.Host + if globalIsDistXL { + addr = GetLocalPeer(endpoints) + } + + return madmin.ServerCPUHardwareInfo{ + Addr: addr, + CPUInfo: cpuHardwares, + } +} diff --git a/cmd/notification.go b/cmd/notification.go index 0255bde24..e09521fc8 100644 --- a/cmd/notification.go +++ b/cmd/notification.go @@ -1056,6 +1056,29 @@ func (sys *NotificationSys) CPULoadInfo() []ServerCPULoadInfo { return reply } +// CPUInfo - CPU Hardware info +func (sys *NotificationSys) CPUInfo() []madmin.ServerCPUHardwareInfo { + reply := make([]madmin.ServerCPUHardwareInfo, len(sys.peerClients)) + var wg sync.WaitGroup + for i, client := range sys.peerClients { + if client == nil { + continue + } + wg.Add(1) + go func(client *peerRESTClient, idx int) { + defer wg.Done() + cpui, err := client.CPUInfo() + if err != nil { + cpui.Addr = client.host.String() + cpui.Error = err.Error() + } + reply[idx] = cpui + }(client, i) + } + wg.Wait() + return reply +} + // NewNotificationSys - creates new notification system object. func NewNotificationSys(config *serverConfig, endpoints EndpointList) *NotificationSys { targetList := getNotificationTargets(config) diff --git a/cmd/peer-rest-client.go b/cmd/peer-rest-client.go index d562e7738..dd50ac050 100644 --- a/cmd/peer-rest-client.go +++ b/cmd/peer-rest-client.go @@ -172,6 +172,17 @@ func (client *peerRESTClient) CPULoadInfo() (info ServerCPULoadInfo, err error) return info, err } +// CpuInfo - fetch CPU hardware information for a remote node. +func (client *peerRESTClient) CPUInfo() (info madmin.ServerCPUHardwareInfo, err error) { + respBody, err := client.call(peerRESTMethodHardwareCPUInfo, nil, nil, -1) + if err != nil { + return + } + defer http.DrainBody(respBody) + err = gob.NewDecoder(respBody).Decode(&info) + return info, err +} + // DrivePerfInfo - fetch Drive performance information for a remote node. func (client *peerRESTClient) DrivePerfInfo(size int64) (info madmin.ServerDrivesPerfInfo, err error) { params := make(url.Values) diff --git a/cmd/peer-rest-common.go b/cmd/peer-rest-common.go index 52c9f07d9..36b30b551 100644 --- a/cmd/peer-rest-common.go +++ b/cmd/peer-rest-common.go @@ -16,7 +16,7 @@ package cmd -const peerRESTVersion = "v4" +const peerRESTVersion = "v5" const peerRESTPath = minioReservedBucketPath + "/peer/" + peerRESTVersion const ( @@ -52,6 +52,7 @@ const ( peerRESTMethodBucketLifecycleSet = "setbucketlifecycle" peerRESTMethodBucketLifecycleRemove = "removebucketlifecycle" peerRESTMethodLog = "log" + peerRESTMethodHardwareCPUInfo = "cpuhardwareinfo" ) const ( diff --git a/cmd/peer-rest-server.go b/cmd/peer-rest-server.go index 196e14ede..1fdb394de 100644 --- a/cmd/peer-rest-server.go +++ b/cmd/peer-rest-server.go @@ -424,6 +424,20 @@ func (s *peerRESTServer) CPULoadInfoHandler(w http.ResponseWriter, r *http.Reque logger.LogIf(ctx, gob.NewEncoder(w).Encode(info)) } +// CPUInfoHandler - returns CPU Hardware info. +func (s *peerRESTServer) CPUInfoHandler(w http.ResponseWriter, r *http.Request) { + if !s.IsValid(w, r) { + s.writeErrorResponse(w, errors.New("Invalid request")) + return + } + + ctx := newContext(r, w, "CPUInfo") + info := getLocalCPUInfo(globalEndpoints, r) + + defer w.(http.Flusher).Flush() + logger.LogIf(ctx, gob.NewEncoder(w).Encode(info)) +} + // DrivePerfInfoHandler - returns Drive Performance info. func (s *peerRESTServer) DrivePerfInfoHandler(w http.ResponseWriter, r *http.Request) { if !s.IsValid(w, r) { @@ -975,6 +989,7 @@ func registerPeerRESTHandlers(router *mux.Router) { subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodCPULoadInfo).HandlerFunc(httpTraceHdrs(server.CPULoadInfoHandler)) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodMemUsageInfo).HandlerFunc(httpTraceHdrs(server.MemUsageInfoHandler)) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDrivePerfInfo).HandlerFunc(httpTraceHdrs(server.DrivePerfInfoHandler)).Queries(restQueries(peerRESTDrivePerfSize)...) + subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodHardwareCPUInfo).HandlerFunc(httpTraceHdrs(server.CPUInfoHandler)) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodDeleteBucket).HandlerFunc(httpTraceHdrs(server.DeleteBucketHandler)).Queries(restQueries(peerRESTBucket)...) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodSignalService).HandlerFunc(httpTraceHdrs(server.SignalServiceHandler)).Queries(restQueries(peerRESTSignal)...) subrouter.Methods(http.MethodPost).Path(SlashSeparator + peerRESTMethodServerUpdate).HandlerFunc(httpTraceHdrs(server.ServerUpdateHandler)).Queries(restQueries(peerRESTUpdateURL, peerRESTSha256Hex, peerRESTLatestRelease)...) diff --git a/go.mod b/go.mod index 3cb38a6d1..0c8a8b1a7 100644 --- a/go.mod +++ b/go.mod @@ -58,6 +58,7 @@ require ( github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 github.com/rjeczalik/notify v0.9.2 github.com/rs/cors v1.6.0 + github.com/shirou/gopsutil v2.18.12+incompatible github.com/sirupsen/logrus v1.4.2 github.com/skyrings/skyring-common v0.0.0-20160929130248-d1c0bb1cbd5e github.com/streadway/amqp v0.0.0-20190402114354-16ed540749f6 diff --git a/go.sum b/go.sum index 87db7c1fd..3c6cad69b 100644 --- a/go.sum +++ b/go.sum @@ -579,6 +579,8 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/go-prompt v0.0.0-20161017233205-f0d19b6901ad/go.mod h1:B3ehdD1xPoWDKgrQgUaGk+m8H1xb1J5TyYDfKpKNeEE= github.com/segmentio/go-prompt v1.2.1-0.20161017233205-f0d19b6901ad/go.mod h1:B3ehdD1xPoWDKgrQgUaGk+m8H1xb1J5TyYDfKpKNeEE= +github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM= +github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= diff --git a/pkg/madmin/README.md b/pkg/madmin/README.md index feaebe9a1..a8eaa342d 100644 --- a/pkg/madmin/README.md +++ b/pkg/madmin/README.md @@ -42,13 +42,14 @@ func main() { } ``` -| Service operations | Info operations | Healing operations | Config operations | Top operations | IAM operations | Misc | KMS | -|:------------------------------------|:------------------------------------------------|:-------------------|:--------------------------|:------------------------|:--------------------------------------|:--------------------------------------------------|:--------------------------------| -| [`ServiceRestart`](#ServiceRestart) | [`ServerInfo`](#ServerInfo) | [`Heal`](#Heal) | [`GetConfig`](#GetConfig) | [`TopLocks`](#TopLocks) | [`AddUser`](#AddUser) | | [`GetKeyStatus`](#GetKeyStatus) | -| [`ServiceStop`](#ServiceStop) | [`ServerCPULoadInfo`](#ServerCPULoadInfo) | | [`SetConfig`](#SetConfig) | | [`SetUserPolicy`](#SetUserPolicy) | [`StartProfiling`](#StartProfiling) | | -| | [`ServerMemUsageInfo`](#ServerMemUsageInfo) | | | | [`ListUsers`](#ListUsers) | [`DownloadProfilingData`](#DownloadProfilingData) | | -| [`ServiceTrace`](#ServiceTrace) | [`ServerDrivesPerfInfo`](#ServerDrivesPerfInfo) | | | | [`AddCannedPolicy`](#AddCannedPolicy) | [`ServerUpdate`](#ServerUpdate) | | -| | [`NetPerfInfo`](#NetPerfInfo) | | | | | | | +| Service operations | Info operations | Healing operations | Config operations | Top operations | IAM operations | Misc | KMS | +|:------------------------------------|:---------------------------------------------------|:-------------------|:--------------------------|:------------------------|:--------------------------------------|:--------------------------------------------------|:--------------------------------| +| [`ServiceRestart`](#ServiceRestart) | [`ServerInfo`](#ServerInfo) | [`Heal`](#Heal) | [`GetConfig`](#GetConfig) | [`TopLocks`](#TopLocks) | [`AddUser`](#AddUser) | | [`GetKeyStatus`](#GetKeyStatus) | +| [`ServiceStop`](#ServiceStop) | [`ServerCPULoadInfo`](#ServerCPULoadInfo) | | [`SetConfig`](#SetConfig) | | [`SetUserPolicy`](#SetUserPolicy) | [`StartProfiling`](#StartProfiling) | | +| | [`ServerMemUsageInfo`](#ServerMemUsageInfo) | | | | [`ListUsers`](#ListUsers) | [`DownloadProfilingData`](#DownloadProfilingData) | | +| [`ServiceTrace`](#ServiceTrace) | [`ServerDrivesPerfInfo`](#ServerDrivesPerfInfo) | | | | [`AddCannedPolicy`](#AddCannedPolicy) | [`ServerUpdate`](#ServerUpdate) | | +| | [`NetPerfInfo`](#NetPerfInfo) | | | | | | | +| | [`ServerCPUHardwareInfo`](#ServerCPUHardwareInfo) | | | | | | | ## 1. Constructor @@ -286,6 +287,33 @@ Fetches network performance of all cluster nodes using given sized payload. Retu | `Error` | _string_ | Errors (if any) encountered while reaching this node | | `ReadThroughput` | _uint64_ | Network read throughput of the server in bytes per second | + +### ServerCPUHardwareInfo() ([]ServerCPUHardwareInfo, error) + +Fetches hardware information of CPU. + +| Param | Type | Description | +|-------------------|---------------------|---------------------------------------------------------------------| +| `hwi.Addr` | _string_ | Address of the server the following information is retrieved from. | +| `hwi.Error` | _string_ | Errors (if any) encountered while reaching this node | +| `hwi.CPUInfo` | _[]cpu.InfoStat_ | The CPU hardware info. | + +| Param | Type | Description | +|----------------------------|----------|--------------------------------------------------------| +| `CPUInfo.CPU` | _int32_ | CPU | +| `CPUInfo.VendorID` | _string_ | Vendor Id | +| `CPUInfo.Family` | _string_ | CPU Family | +| `CPUInfo.Model` | _string_ | Model | +| `CPUInfo.Stepping` | _int32_ | Stepping | +| `CPUInfo.PhysicalId` | _string_ | Physical Id | +| `CPUInfo.CoreId` | _string_ | Core Id | +| `CPUInfo.Cores` | _int32_ | Cores | +| `CPUInfo.ModelName` | _string_ | Model Name | +| `CPUInfo.Mhz` | _float64_| Mhz | +| `CPUInfo.CacheZSize` | _int32_ | cache sizes | +| `CPUInfo.Flags` |_[]string_| Flags | +| `CPUInfo.Microcode` | _string_ | Micro codes | + ## 5. Heal operations diff --git a/pkg/madmin/examples/hw-cpu-info.go b/pkg/madmin/examples/hw-cpu-info.go new file mode 100644 index 000000000..eafd40e4e --- /dev/null +++ b/pkg/madmin/examples/hw-cpu-info.go @@ -0,0 +1,43 @@ +// +build ignore + +/* + * MinIO Cloud Storage, (C) 2019 MinIO, Inc. + * + * 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 main + +import ( + "github.com/minio/minio/pkg/madmin" + "log" +) + +func main() { + // Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are + // dummy values, please replace them with correct values. + + // API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise. + // New returns an MinIO Admin client object. + madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true) + if err != nil { + log.Fatalln(err) + } + st, err := madmClnt.ServerCPUHardwareInfo() + if err != nil { + log.Fatalln(err) + } + log.Println(st) + +} diff --git a/pkg/madmin/hardware-commands.go b/pkg/madmin/hardware-commands.go new file mode 100644 index 000000000..dba5c6dd2 --- /dev/null +++ b/pkg/madmin/hardware-commands.go @@ -0,0 +1,78 @@ +/* + * MinIO Cloud Storage, (C) 2019 MinIO, Inc. + * + * 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 madmin + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/url" + + "github.com/shirou/gopsutil/cpu" +) + +// HardwareType - type to hardware +type HardwareType string + +const ( + // HARDWARE represents hardware type + HARDWARE = "hwType" + // CPU represents hardware as cpu + CPU HardwareType = "cpu" +) + +// ServerCPUHardwareInfo holds informantion about cpu hardware +type ServerCPUHardwareInfo struct { + Addr string `json:"addr"` + Error string `json:"error,omitempty"` + CPUInfo []cpu.InfoStat `json:"cpu"` +} + +// ServerCPUHardwareInfo - Returns cpu hardware information +func (adm *AdminClient) ServerCPUHardwareInfo() ([]ServerCPUHardwareInfo, error) { + v := url.Values{} + v.Set(HARDWARE, string(CPU)) + resp, err := adm.executeMethod("GET", requestData{ + relPath: "/v1/hardware", + queryValues: v, + }) + + defer closeResponse(resp) + if err != nil { + return nil, err + } + + // Check response http status code + if resp.StatusCode != http.StatusOK { + return nil, httpRespToErrorResponse(resp) + } + + // Unmarshal the server's json response + var cpuInfo []ServerCPUHardwareInfo + + respBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + err = json.Unmarshal(respBytes, &cpuInfo) + if err != nil { + return nil, err + } + return cpuInfo, nil +}