diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23e197a --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.swp +*.swo +/cfg.json +/agent +/var +/falcon-agent* +/falcon_agent +.DS_Store +*.iml +*.idea +/gitversion + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5bb7e0f --- /dev/null +++ b/README.md @@ -0,0 +1,228 @@ +falcon-windows-agent +=== + +open-falcon 的 windows-agent, go 语言编写, 开箱即用 +支持端口监控 +支持进程监控 +支持注册为 windows 服务后台运行 +内置 IIs 监控 和 MsSQL(SqlServer) 监控。 + + +#### 上报字段 +Windows Metrics +-------------------------------- +| Counters | Type |Tag| Notes| +|-----|------|------|------| +|agent.alive |GAUGE|/|ailve | +|cpu.idle |GAUGE|/|cpu idle| +|cpu.busy |GAUGE|/|cpu busy| +|cpu.user |GAUGE|/|cpu user| +|cpu.system |GAUGE|/|cpu system| +|mem.memtotal |GAUGE|/|mem total| +|mem.memused |GAUGE|/|mem used| +|mem.memfree |GAUGE|/|mem free| +|mem.memfree.percent |GAUGE|/|memfree percent| +|mem.memused.percent |GAUGE|/|memused percent| +|df.bytes.total |GAUGE|mounts=Mountpoint,fstype=fstype|device bytes total| +|df.bytes.free |GAUGE|mounts=Mountpoint,fstype=fstype|device bytes free| +|df.bytes.total |GAUGE|mounts=Mountpoint,fstype=fstype|device bytes total| +|df.bytes.used.percent |GAUGE|mounts=Mountpoint,fstype=fstype|device used percent| +|df.bytes.free.percent |GAUGE|mounts=Mountpoint,fstype=fstype|device free percent| +|df.statistics.total |GAUGE|mounts=Mountpoint,fstype=fstype|device statistics total| +|df.statistics.used |GAUGE|mounts=Mountpoint,fstype=fstype|device statistics used| +|df.statistics.used.percent |GAUGE|mounts=Mountpoint,fstype=fstype|device statistics used percent| +|disk.io.msec_read |COUNTER|device=device|disk io msec read| +|disk.io.msec_write |COUNTER|device=device|disk io msec write| +|disk.io.read_bytes |COUNTER|device=device|disk io read bytes| +|disk.io.write bytes |COUNTER|device=device|disk io write bytes| +|disk.io.read_requests |COUNTER|device=device|disk io read requests| +|disk.io.write requests |COUNTER|device=device|disk io write requests| +|disk.io.util |COUNTER|device=device|disk io util| +|net.if.in.bytes |COUNTER|iface=ifname|net if bytes recv| +|net.if.in.packets |COUNTER|iface=ifname|net if packets recv| +|net.if.in.errors |COUNTER|iface=ifname|net if errors recv| +|net.if.in.dropped |COUNTER|iface=ifname|net if dropped recv| +|net.if.out.bytes |COUNTER|iface=ifname|net if bytes sent| +|net.if.out.packets |COUNTER|iface=ifname|net if packets sent| +|net.if.out.errors |COUNTER|iface=ifname|net if errors sent| +|net.if.out.dropped |COUNTER|iface=ifname|net if dropped sent| +|tcpip.confailures |COUNTER|/|tcp connect failure| +|tcpip.conactive |COUNTER|/|tcp connect active| +|tcpip.conpassive |COUNTER|/|tcp connect passive| +|tcpip.conestablished |GAUGE|/|tcp connect established | +|tcpip.conreset |COUNTER|/|tcp connect reset| +|net.port.listen |GAUGE|port=port|port alive| +|proc.num |GAUGE|cmdline=cmdline,proc=proc|proc number| + + +IIs Metrics +-------------------------------- +| Counters | Type |Tag| Notes| +|-----|------|------|------| +|iis.bytes.received |COUNTER|site=site|Bytes Received/sec | +|iis.bytes.sent |COUNTER|site=site|Total Bytes Sent/sec| +|iis.requests.cgi |COUNTER|site=site|CGI Requests/sec| +|iis.requests.copy |COUNTER|site=site|copy Requests/sec| +|iis.requests.delete |COUNTER|site=site|delete Requests/sec| +|iis.requests.get |COUNTER|site=site|get Requests/sec| +|iis.requests.head |COUNTER|site=site|head Requests/sec| +|iis.requests.isapi |COUNTER|site=site|isapi Requests/sec| +|iis.requests.lock |COUNTER|site=site|lock Requests/sec| +|iis.requests.mkcol |COUNTER|site=site|mkcol Requests/sec| +|iis.requests.move |COUNTER|site=site|move Requests/sec| +|iis.requests.options |COUNTER|site=site|options Requests/sec| +|iis.requests.post |COUNTER|site=site|post Requests/sec| +|iis.requests.proppatch |COUNTER|site=site|proppatch Requests/sec| +|iis.requests.propfind |COUNTER|site=site|propfind Requests/sec| +|iis.requests.put |COUNTER|site=site|put Requests/sec| +|iis.requests.search |COUNTER|site=site|search Requests/sec| +|iis.requests.trace |COUNTER|site=site|trace Requests/sec| +|iis.requests.unlock |COUNTER|site=site|unlock Requests/sec| +|iis.errors.notfount |COUNTER|site=site|notfound errors/sec| +|iis.errors.locked |COUNTER|site=site|locked errors/sec| +|iis.connection.attempts |COUNTER|site=site|conn attempts/sec| +|iis.connections |GAUGE|site=site|connections| +|iis.service.uptime |GAUGE|site=site|Service Uptime| + +视版本和配置不同,采集到的 Metric 可能有所差别。 + +MsSQL +-------------------------------- +| Counters | Type |Tag| Notes| +|-----|------|------|------| +|MsSQL.Lock_Waits/sec |GAUGE|instance=instance|Lock_Waits/sec| +|MsSQL.Log_File(s)_Size_(KB) |GAUGE|instance=instance|Log_File(s)_Size_(KB)| +|MsSQL.Log_File(s)_Used_Size_(KB) |GAUGE|instance=instance|Log_File(s)_Used_Size_(KB)| +|MsSQL.Percent_Log_Used |GAUGE|instance=instance|Log_File(s)_Used_Size_(KB)| +|MsSQL.Errors/sec |GAUGE|error_type=error_type|Log_File(s)_Used_Size_(KB)| +|MsSQL.Batch_Requests/sec |GAUGE|/|Batch_Requests/sec| +|MsSQL.Target_Server_Memory_(KB) |GAUGE|/|Target_Server_Memory_(KB)| +|MsSQL.Total_Server_Memory_(KB) |GAUGE|/|Total_Server_Memory_(KB)| +|MsSQL.IO_requests |GAUGE|/|IO_requests| +|MsSQL.Connection |GAUGE|/|Connections| +|MsSQL.Uptime |GAUGE|/|Service Uptime| + +视版本和配置不同,采集到的 Metric 可能有所差别。 +其中Lock_Waits/sec …… Total_Server_Memory_(KB) 等通过查询 sys.dm_os_performance_counters 表获得,这需要服务器上开启性能计数器。 + +如果这部分指标缺失,请确认性能计数器是否正确开启。 + + + +#### 使用方式 + + +配置文件请参照cfg.example.json,修改该文件名为cfg.json + +``` +{ + "debug": true, + "logfile": "windows.log", //日志的输出路径 + "hostname": "", + "ip": "", + "iis":{ + "enabled": false, + "websites": [ + "Default Web Site" //web 的站点,可以留空,默认会采集_Total的 + ] + }, + "mssql":{ + "enabled": false, + "addr":"127.0.0.1", + "port":1433, + "username":"sa", + "password":"123456", + "encrypt":"disable", + //disable - 不加密 + //false - 除认证报文外不加密 + //true -加密 + //SQL Server 2008 和 SQL Server 2008 R2 必须选择 disable,否则无法正常认证。要修复这个问题,需要升级 SQL Server 2008 R2 SP2,或 SQL Server 2008 SP3 + "instance": [ //要采集数据库实例名 + "test" + ] + }, + "heartbeat": { + "enabled": true, + "addr": "127.0.0.1:6030", + "interval": 60, + "timeout": 1000 + }, + "transfer": { + "enabled": true, + "addrs": [ + "127.0.0.1:8433" + ], + "interval": 60, + "timeout": 1000 + }, + "http": { + "enabled": true, + "listen": ":1988", + "backdoor": false + }, + "collector": { + "ifacePrefix": ["Intel"] //所采集的网卡描述信息关键词,例如Intel(R)PRO/1000 MT NetworkConnection + }, + "ignore": { + "cpu.busy": true, + } +} + +``` + +#### http 信息维护接口 + +``` +curl http://127.0.0.1:1988/health +正常则返回 ok + +curl http://127.0.0.1:1988/version +返回版本 + +curl http://127.0.0.1:1988/workdir +返回工作目录 + +curl http://127.0.0.1:1988/config +返回配置 +``` + +#### http 转发接口 +``` +http://127.0.0.1:1990//v1/push +``` +#### 源码安装 + +``` +cd %GOPATH%/src/github.com/freedomkk-qfeng/windows-agent +go get ./... +go build -o windows-agent.exe + +``` + +#### 运行 +以下命令需在管理员模式下运行开 cmd 或 Powershell + +先试运行一下 +``` +.\windows-agent.exe +2016/08/08 13:44:31 cfg.go:96: read config file: cfg.json successfully +2016/08/08 13:44:31 var.go:31: logging on windows.log +2016/08/08 13:44:31 http.go:64: listening :1988 +``` +等待1-2分钟,观察输出,确认运行正常 +使用 [nssm](https://nssm.cc/) 注册为 Windows 服务。 + +``` +.\nssm.exe install windows-agent +``` +![](http://i.imgur.com/SOhBSBo.png) + + +启动服务 +``` +.\nssm.exe start windows-agent +``` + + +#### TODO +增加完事 sqlserver 的监控项 diff --git a/cfg.example.json b/cfg.example.json new file mode 100644 index 0000000..82771e1 --- /dev/null +++ b/cfg.example.json @@ -0,0 +1,48 @@ +{ + "debug": true, + "logfile": "windows.log", + "hostname": "", + "ip": "", + "iis":{ + "enabled": false, + "websites": [ + "Default Web Site" + ] + }, + "mssql":{ + "enabled": false, + "addr":"127.0.0.1", + "port":1433, + "username":"sa", + "password":"123456", + "encrypt":"disable", + "instance": [ + "test" + ] + }, + "heartbeat": { + "enabled": true, + "addr": "127.0.0.1:6030", + "interval": 60, + "timeout": 1000 + }, + "transfer": { + "enabled": true, + "addrs": [ + "127.0.0.1:8433" + ], + "interval": 60, + "timeout": 1000 + }, + "http": { + "enabled": true, + "listen": ":1988", + "backdoor": false + }, + "collector": { + "ifacePrefix": ["Intel"] + }, + "ignore": { + "cpu.busy": true + } +} diff --git a/cron/builtin.go b/cron/builtin.go new file mode 100644 index 0000000..7ab7d02 --- /dev/null +++ b/cron/builtin.go @@ -0,0 +1,100 @@ +package cron + +import ( + "strconv" + "strings" + "time" + + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/open-falcon/common/model" +) + +func SyncBuiltinMetrics() { + if g.Config().Heartbeat.Enabled && g.Config().Heartbeat.Addr != "" { + go syncBuiltinMetrics() + } +} + +func syncBuiltinMetrics() { + + var timestamp int64 = -1 + var checksum string = "nil" + + duration := time.Duration(g.Config().Heartbeat.Interval) * time.Second + + for { + time.Sleep(duration) + + var ports = []int64{} + var paths = []string{} + var procs = make(map[string]map[int]string) + var urls = make(map[string]string) + + hostname, err := g.Hostname() + if err != nil { + continue + } + + req := model.AgentHeartbeatRequest{ + Hostname: hostname, + Checksum: checksum, + } + + var resp model.BuiltinMetricResponse + err = g.HbsClient.Call("Agent.BuiltinMetrics", req, &resp) + if err != nil { + g.Logger().Println("ERROR:", err) + continue + } + + if resp.Timestamp <= timestamp { + continue + } + + if resp.Checksum == checksum { + continue + } + + timestamp = resp.Timestamp + checksum = resp.Checksum + + for _, metric := range resp.Metrics { + if metric.Metric == g.NET_PORT_LISTEN { + arr := strings.Split(metric.Tags, "=") + if len(arr) != 2 { + continue + } + + if port, err := strconv.ParseInt(arr[1], 10, 64); err == nil { + ports = append(ports, port) + } else { + g.Logger().Println("metrics ParseInt failed:", err) + } + + continue + } + + if metric.Metric == g.PROC_NUM { + arr := strings.Split(metric.Tags, ",") + + tmpMap := make(map[int]string) + + for i := 0; i < len(arr); i++ { + if strings.HasPrefix(arr[i], "name=") { + tmpMap[1] = strings.TrimSpace(arr[i][5:]) + } else if strings.HasPrefix(arr[i], "cmdline=") { + tmpMap[2] = strings.TrimSpace(arr[i][8:]) + } + } + + procs[metric.Tags] = tmpMap + } + } + + g.SetReportUrls(urls) + g.SetReportPorts(ports) + g.SetReportProcs(procs) + g.SetDuPaths(paths) + + } +} diff --git a/cron/collector.go b/cron/collector.go new file mode 100644 index 0000000..3b8d736 --- /dev/null +++ b/cron/collector.go @@ -0,0 +1,75 @@ +package cron + +import ( + "time" + + "github.com/freedomkk-qfeng/windows-agent/funcs" + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/open-falcon/common/model" +) + +func InitDataHistory() { + for { + funcs.UpdateCpuStat() + time.Sleep(g.COLLECT_INTERVAL) + } +} + +func Collect() { + + if !g.Config().Transfer.Enabled { + return + } + + if len(g.Config().Transfer.Addrs) == 0 { + return + } + + for _, v := range funcs.Mappers { + go collect(int64(v.Interval), v.Fs) + } +} + +func collect(sec int64, fns []func() []*model.MetricValue) { + t := time.NewTicker(time.Second * time.Duration(sec)).C + for { + <-t + + hostname, err := g.Hostname() + if err != nil { + continue + } + + mvs := []*model.MetricValue{} + ignoreMetrics := g.Config().IgnoreMetrics + + for _, fn := range fns { + items := fn() + if items == nil { + continue + } + + if len(items) == 0 { + continue + } + + for _, mv := range items { + if b, ok := ignoreMetrics[mv.Metric]; ok && b { + continue + } else { + mvs = append(mvs, mv) + } + } + } + + now := time.Now().Unix() + for j := 0; j < len(mvs); j++ { + mvs[j].Step = sec + mvs[j].Endpoint = hostname + mvs[j].Timestamp = now + } + + g.SendToTransfer(mvs) + + } +} diff --git a/cron/ips.go b/cron/ips.go new file mode 100644 index 0000000..e261ed3 --- /dev/null +++ b/cron/ips.go @@ -0,0 +1,32 @@ +package cron + +import ( + "time" + + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/open-falcon/common/model" +) + +func SyncTrustableIps() { + if g.Config().Heartbeat.Enabled && g.Config().Heartbeat.Addr != "" { + go syncTrustableIps() + } +} + +func syncTrustableIps() { + + duration := time.Duration(g.Config().Heartbeat.Interval) * time.Second + + for { + time.Sleep(duration) + + var ips string + err := g.HbsClient.Call("Agent.TrustableIps", model.NullRpcRequest{}, &ips) + if err != nil { + g.Logger().Println("ERROR: call Agent.TrustableIps fail", err) + continue + } + + g.SetTrustableIps(ips) + } +} diff --git a/cron/reporter.go b/cron/reporter.go new file mode 100644 index 0000000..7114158 --- /dev/null +++ b/cron/reporter.go @@ -0,0 +1,39 @@ +package cron + +import ( + "fmt" + + "time" + + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/open-falcon/common/model" +) + +func ReportAgentStatus() { + if g.Config().Heartbeat.Enabled && g.Config().Heartbeat.Addr != "" { + go reportAgentStatus(time.Duration(g.Config().Heartbeat.Interval) * time.Second) + } +} + +func reportAgentStatus(interval time.Duration) { + for { + hostname, err := g.Hostname() + if err != nil { + hostname = fmt.Sprintf("error:%s", err.Error()) + } + + req := model.AgentReportRequest{ + Hostname: hostname, + IP: g.IP(), + AgentVersion: g.VERSION, + } + + var resp model.SimpleRpcResponse + err = g.HbsClient.Call("Agent.ReportStatus", req, &resp) + if err != nil || resp.Code != 0 { + g.Logger().Println("call Agent.ReportStatus fail:", err, "Request:", req, "Response:", resp) + } + + time.Sleep(interval) + } +} diff --git a/funcs/agent.go b/funcs/agent.go new file mode 100644 index 0000000..cd49c4f --- /dev/null +++ b/funcs/agent.go @@ -0,0 +1,9 @@ +package funcs + +import ( + "github.com/open-falcon/common/model" +) + +func AgentMetrics() []*model.MetricValue { + return []*model.MetricValue{GaugeValue("agent.alive", 1)} +} diff --git a/funcs/checker.go b/funcs/checker.go new file mode 100644 index 0000000..5fbd8d9 --- /dev/null +++ b/funcs/checker.go @@ -0,0 +1,21 @@ +package funcs + +import ( + "fmt" +) + +func CheckCollector() { + + output := make(map[string]bool) + + output["df.bytes"] = len(DeviceMetrics()) > 0 + output["memory "] = len(MemMetrics()) > 0 + + for k, v := range output { + status := "fail" + if v { + status = "ok" + } + fmt.Println(k, "...", status) + } +} diff --git a/funcs/common.go b/funcs/common.go new file mode 100644 index 0000000..49a6d0f --- /dev/null +++ b/funcs/common.go @@ -0,0 +1,30 @@ +package funcs + +import ( + "github.com/open-falcon/common/model" + "strings" +) + +func NewMetricValue(metric string, val interface{}, dataType string, tags ...string) *model.MetricValue { + mv := model.MetricValue{ + Metric: metric, + Value: val, + Type: dataType, + } + + size := len(tags) + + if size > 0 { + mv.Tags = strings.Join(tags, ",") + } + + return &mv +} + +func GaugeValue(metric string, val interface{}, tags ...string) *model.MetricValue { + return NewMetricValue(metric, val, "GAUGE", tags...) +} + +func CounterValue(metric string, val interface{}, tags ...string) *model.MetricValue { + return NewMetricValue(metric, val, "COUNTER", tags...) +} diff --git a/funcs/cpustat.go b/funcs/cpustat.go new file mode 100644 index 0000000..7404441 --- /dev/null +++ b/funcs/cpustat.go @@ -0,0 +1,93 @@ +package funcs + +import ( + "sync" + + "github.com/open-falcon/common/model" +) + +const ( + historyCount int = 2 +) + +var ( + procStatHistory [historyCount]*CPUTimesStat + psLock = new(sync.RWMutex) +) + +func UpdateCpuStat() error { + ps, err := CPUTimes(false) + if err != nil { + return err + } + + psLock.Lock() + defer psLock.Unlock() + for i := historyCount - 1; i > 0; i-- { + procStatHistory[i] = procStatHistory[i-1] + } + + procStatHistory[0] = &ps[0] + + return nil +} + +func deltaTotal() float64 { + if procStatHistory[1] == nil { + return 0 + } + return procStatHistory[0].Total - procStatHistory[1].Total +} + +func CpuIdle() float64 { + psLock.RLock() + defer psLock.RUnlock() + dt := deltaTotal() + if dt == 0 { + return 0.0 + } + invQuotient := 100.00 / float64(dt) + return float64(procStatHistory[0].Idle-procStatHistory[1].Idle) * invQuotient +} + +func CpuUser() float64 { + psLock.RLock() + defer psLock.RUnlock() + dt := deltaTotal() + if dt == 0 { + return 0.0 + } + invQuotient := 100.00 / float64(dt) + return float64(procStatHistory[0].User-procStatHistory[1].User) * invQuotient +} + +func CpuSystem() float64 { + psLock.RLock() + defer psLock.RUnlock() + dt := deltaTotal() + if dt == 0 { + return 0.0 + } + invQuotient := 100.00 / float64(dt) + return float64(procStatHistory[0].System-procStatHistory[1].System) * invQuotient +} + +func CpuPrepared() bool { + psLock.RLock() + defer psLock.RUnlock() + return procStatHistory[1] != nil +} + +func CpuMetrics() []*model.MetricValue { + if !CpuPrepared() { + return []*model.MetricValue{} + } + + cpuIdleVal := CpuIdle() + idle := GaugeValue("cpu.idle", cpuIdleVal) + busy := GaugeValue("cpu.busy", 100.0-cpuIdleVal) + user := GaugeValue("cpu.user", CpuUser()) + system := GaugeValue("cpu.system", CpuSystem()) + + return []*model.MetricValue{idle, user, busy, system} +} diff --git a/funcs/cputimes.go b/funcs/cputimes.go new file mode 100644 index 0000000..d9a96dd --- /dev/null +++ b/funcs/cputimes.go @@ -0,0 +1,74 @@ +package funcs + +import ( + "syscall" + + "unsafe" +) + +var ( + Modkernel32 = syscall.NewLazyDLL("kernel32.dll") + ModNt = syscall.NewLazyDLL("ntdll.dll") + ModPdh = syscall.NewLazyDLL("pdh.dll") + + ProcGetSystemTimes = Modkernel32.NewProc("GetSystemTimes") + ProcNtQuerySystemInformation = ModNt.NewProc("NtQuerySystemInformation") + PdhOpenQuery = ModPdh.NewProc("PdhOpenQuery") + PdhAddCounter = ModPdh.NewProc("PdhAddCounterW") + PdhCollectQueryData = ModPdh.NewProc("PdhCollectQueryData") + PdhGetFormattedCounterValue = ModPdh.NewProc("PdhGetFormattedCounterValue") + PdhCloseQuery = ModPdh.NewProc("PdhCloseQuery") +) + +type FILETIME struct { + DwLowDateTime uint32 + DwHighDateTime uint32 +} + +type CPUTimesStat struct { + User float64 `json:"user"` + System float64 `json:"system"` + Idle float64 `json:"idle"` + Total float64 `json:"total"` +} + +type Win32_Processor struct { + LoadPercentage *uint16 + Family uint16 + Manufacturer string + Name string + NumberOfLogicalProcessors uint32 + ProcessorId *string + Stepping *string + MaxClockSpeed uint32 +} + +func CPUTimes(percpu bool) ([]CPUTimesStat, error) { + var ret []CPUTimesStat + + var lpIdleTime FILETIME + var lpKernelTime FILETIME + var lpUserTime FILETIME + r, _, _ := ProcGetSystemTimes.Call( + uintptr(unsafe.Pointer(&lpIdleTime)), + uintptr(unsafe.Pointer(&lpKernelTime)), + uintptr(unsafe.Pointer(&lpUserTime))) + if r == 0 { + return ret, syscall.GetLastError() + } + + LOT := float64(0.0000001) + HIT := (LOT * 4294967296.0) + idle := ((HIT * float64(lpIdleTime.DwHighDateTime)) + (LOT * float64(lpIdleTime.DwLowDateTime))) + user := ((HIT * float64(lpUserTime.DwHighDateTime)) + (LOT * float64(lpUserTime.DwLowDateTime))) + kernel := ((HIT * float64(lpKernelTime.DwHighDateTime)) + (LOT * float64(lpKernelTime.DwLowDateTime))) + system := (kernel - idle) + + ret = append(ret, CPUTimesStat{ + Idle: float64(idle), + User: float64(user), + System: float64(system), + Total: float64(idle) + float64(user) + float64(system), + }) + return ret, nil +} diff --git a/funcs/device.go b/funcs/device.go new file mode 100644 index 0000000..9c86d28 --- /dev/null +++ b/funcs/device.go @@ -0,0 +1,57 @@ +package funcs + +import ( + "fmt" + + "github.com/freedomkk-qfeng/windows-agent/g" + + "github.com/open-falcon/common/model" + "github.com/shirou/gopsutil/disk" +) + +func disk_usage(path string) (*disk.UsageStat, error) { + disk_usage, err := disk.Usage(path) + return disk_usage, err +} + +func disk_Partitions() ([]disk.PartitionStat, error) { + disk_Partitions, err := disk.Partitions(true) + return disk_Partitions, err +} +func DeviceMetrics() (L []*model.MetricValue) { + diskPartitions, err := disk_Partitions() + + if err != nil { + g.Logger().Println(err) + return + } + + var diskTotal uint64 = 0 + var diskUsed uint64 = 0 + + for _, device := range diskPartitions { + du, err := disk_usage(device.Mountpoint) + if err != nil { + g.Logger().Println(err) + continue + } + + diskTotal += du.Total + diskUsed += du.Used + + tags := fmt.Sprintf("mount=%s,fstype=%s", device.Mountpoint, device.Fstype) + L = append(L, GaugeValue("df.bytes.total", du.Total, tags)) + L = append(L, GaugeValue("df.bytes.used", du.Used, tags)) + L = append(L, GaugeValue("df.bytes.free", du.Free, tags)) + L = append(L, GaugeValue("df.bytes.used.percent", du.UsedPercent, tags)) + L = append(L, GaugeValue("df.bytes.free.percent", 100-du.UsedPercent, tags)) + } + + if len(L) > 0 && diskTotal > 0 { + L = append(L, GaugeValue("df.statistics.total", float64(diskTotal))) + L = append(L, GaugeValue("df.statistics.used", float64(diskUsed))) + L = append(L, GaugeValue("df.statistics.used.percent", float64(diskUsed)*100.0/float64(diskTotal))) + } + + return +} diff --git a/funcs/diskiocounter.go b/funcs/diskiocounter.go new file mode 100644 index 0000000..141d10f --- /dev/null +++ b/funcs/diskiocounter.go @@ -0,0 +1,89 @@ +package funcs + +import ( + "github.com/StackExchange/wmi" +) + +type Win32_PerfFormattedData struct { + AvgDiskSecPerRead_Base uint32 + AvgDiskSecPerWrite_Base uint32 + DiskReadBytesPerSec uint64 + DiskReadsPerSec uint32 + DiskWriteBytesPerSec uint64 + DiskWritesPerSec uint32 + Name string +} + +type Win32_PerfFormattedData_IDLE struct { + Name string + PercentIdleTime uint64 +} + +type diskIOCounter struct { + Msec_Read uint32 + Msec_Write uint32 + Read_Bytes uint64 + Read_Requests uint32 + Write_Bytes uint64 + Write_Requests uint32 + Util uint64 +} + +func PerfFormattedData() ([]Win32_PerfFormattedData, error) { + + var dst []Win32_PerfFormattedData + Query := `SELECT + AvgDiskSecPerRead_Base, + AvgDiskSecPerWrite_Base, + DiskReadBytesPerSec, + DiskReadsPerSec, + DiskWriteBytesPerSec, + DiskWritesPerSec, + Name + FROM Win32_PerfRawData_PerfDisk_PhysicalDisk` + err := wmi.Query(Query, &dst) + + return dst, err +} + +func PerfFormattedData_IDLE() ([]Win32_PerfFormattedData_IDLE, error) { + + var dst []Win32_PerfFormattedData_IDLE + + err := wmi.Query("SELECT PercentIdleTime FROM Win32_PerfFormattedData_PerfDisk_PhysicalDisk ", &dst) + + return dst, err +} + +func IOCounters() (map[string]diskIOCounter, error) { + ret := make(map[string]diskIOCounter, 0) + dst, err := PerfFormattedData() + if err == nil { + for _, d := range dst { + if d.Name == "_Total" { // not get _Total + continue + } + ret[d.Name] = diskIOCounter{ + Msec_Read: d.AvgDiskSecPerRead_Base, + Msec_Write: d.AvgDiskSecPerWrite_Base, + Read_Bytes: d.DiskReadBytesPerSec, + Read_Requests: d.DiskReadsPerSec, + Write_Bytes: d.DiskWriteBytesPerSec, + Write_Requests: d.DiskWritesPerSec, + Util: 0, + } + } + } + dstIdle, err := PerfFormattedData_IDLE() + if err == nil { + for _, dd := range dstIdle { + if dd.Name == "_Total" { + continue + } + result := ret[dd.Name] + result.Util = dd.PercentIdleTime + ret[dd.Name] = result + } + } + return ret, err +} diff --git a/funcs/diskstat.go b/funcs/diskstat.go new file mode 100644 index 0000000..70a6bb2 --- /dev/null +++ b/funcs/diskstat.go @@ -0,0 +1,28 @@ +package funcs + +import ( + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/open-falcon/common/model" +) + +func DiskIOMetrics() (L []*model.MetricValue) { + + disk_iocounter, err := IOCounters() + if err != nil { + g.Logger().Println(err) + return + } + + for device, ds := range disk_iocounter { + + device := "device=" + device + L = append(L, CounterValue("disk.io.msec_read", ds.Msec_Read, device)) + L = append(L, CounterValue("disk.io.msec_write", ds.Msec_Write, device)) + L = append(L, CounterValue("disk.io.read_bytes", ds.Read_Bytes, device)) + L = append(L, CounterValue("disk.io.read_requests", ds.Read_Requests, device)) + L = append(L, CounterValue("disk.io.write_bytes", ds.Write_Bytes, device)) + L = append(L, CounterValue("disk.io.write_requests", ds.Write_Requests, device)) + L = append(L, GaugeValue("disk.io.util", 100-ds.Util, device)) + } + return +} diff --git a/funcs/func_test.go b/funcs/func_test.go new file mode 100644 index 0000000..b63ea2b --- /dev/null +++ b/funcs/func_test.go @@ -0,0 +1,122 @@ +package funcs + +import ( + "testing" +) + +const ( + server = "192.168.11.128" + port = 1433 + user = "sa" + password = "ecNU10@$" + encrypt = "disable" +) + +func Test_cpu(t *testing.T) { + times, err := CPUTimes(false) + t.Log(times) + t.Error(err) +} + +func Test_mem_info(t *testing.T) { + meminfo, err := mem_info() + t.Log(meminfo) + t.Error(err) +} + +func Test_disk(t *testing.T) { + diskPartitions, err := disk_Partitions() + t.Log(diskPartitions) + t.Error(err) + diskUsage, err := disk_usage("E:") + t.Log(diskUsage) + t.Error(err) +} + +func Test_net_status(t *testing.T) { + var ifacePrefix = []string{"Intel"} + netifs, err := net_status(ifacePrefix) + t.Log(netifs) + t.Error(err) +} + +func Test_IsTCPPortUsed(t *testing.T) { + res := IsTCPPortUsed(80) + t.Log(res) +} + +func Test_TestIOCounters(t *testing.T) { + r, err := PerfFormattedData() + t.Log(r) + t.Error(err) + ret, err := IOCounters() + t.Log(ret) + t.Error(err) +} + +func Test_Process(t *testing.T) { + // p, err := Processes() + // t.Log(p) + // t.Error(err) +} +func Test_tcpip(t *testing.T) { + ret, _ := TcpipCounters() + t.Log(ret) +} + +func Test_iis_status(t *testing.T) { + result, err := IIsCounters() + t.Error(err) + t.Log(result) +} + +func Test_in_array(t *testing.T) { + instance := []string{"test"} + re := in_array("test", instance) + t.Log(re) +} + +func Test_performance_query(t *testing.T) { + instance := []string{"_Total", "test"} + db, err := mssql_conn(server, port, user, password, encrypt) + if err != nil { + t.Error(err) + } + result, err := performance_query(db, instance) + t.Log(result) + t.Error(err) + db.Close() +} + +func Test_io_req_query(t *testing.T) { + db, err := mssql_conn(server, port, user, password, encrypt) + if err != nil { + t.Error(err) + } + result, err := io_req_query(db) + t.Log(result) + t.Error(err) + db.Close() +} + +func Test_conn_query(t *testing.T) { + db, err := mssql_conn(server, port, user, password, encrypt) + if err != nil { + t.Error(err) + } + result, err := conn_query(db) + t.Log(result) + t.Error(err) + db.Close() +} + +func Test_uptime_query(t *testing.T) { + db, err := mssql_conn(server, port, user, password, encrypt) + if err != nil { + t.Error(err) + } + result, err := uptime_query(db) + t.Log(result) + t.Error(err) + db.Close() +} diff --git a/funcs/funcs.go b/funcs/funcs.go new file mode 100644 index 0000000..3d333a9 --- /dev/null +++ b/funcs/funcs.go @@ -0,0 +1,45 @@ +package funcs + +import ( + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/open-falcon/common/model" +) + +type FuncsAndInterval struct { + Fs []func() []*model.MetricValue + Interval int +} + +var Mappers []FuncsAndInterval + +func BuildMappers() { + interval := g.Config().Transfer.Interval + Mappers = []FuncsAndInterval{ + FuncsAndInterval{ + Fs: []func() []*model.MetricValue{ + AgentMetrics, + CpuMetrics, + NetMetrics, + MemMetrics, + DeviceMetrics, + DiskIOMetrics, + TcpipMetrics, + }, + Interval: interval, + }, + FuncsAndInterval{ + Fs: []func() []*model.MetricValue{ + PortMetrics, + ProcMetrics, + }, + Interval: interval, + }, + FuncsAndInterval{ + Fs: []func() []*model.MetricValue{ + iisMetrics, + mssqlMetrics, + }, + Interval: interval, + }, + } +} diff --git a/funcs/ifstat.go b/funcs/ifstat.go new file mode 100644 index 0000000..f04c490 --- /dev/null +++ b/funcs/ifstat.go @@ -0,0 +1,50 @@ +package funcs + +import ( + "strings" + + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/open-falcon/common/model" + "github.com/shirou/gopsutil/net" +) + +func net_status(ifacePrefix []string) ([]net.IOCountersStat, error) { + net_iocounter, err := net.IOCounters(true) + netIfs := []net.IOCountersStat{} + for _, iface := range ifacePrefix { + for _, netIf := range net_iocounter { + if strings.Contains(netIf.Name, iface) { + netIfs = append(netIfs, netIf) + } + } + } + return netIfs, err +} + +func NetMetrics() []*model.MetricValue { + return CoreNetMetrics(g.Config().Collector.IfacePrefix) +} + +func CoreNetMetrics(ifacePrefix []string) (L []*model.MetricValue) { + + netIfs, err := net_status(ifacePrefix) + if err != nil { + g.Logger().Println(err) + return []*model.MetricValue{} + } + + for _, netIf := range netIfs { + iface := "iface=" + netIf.Name + L = append(L, CounterValue("net.if.in.bytes", netIf.BytesRecv, iface)) //此处乘以8即为bit的流量 + L = append(L, CounterValue("net.if.in.packets", netIf.PacketsRecv, iface)) + L = append(L, CounterValue("net.if.in.errors", netIf.Errin, iface)) + L = append(L, CounterValue("net.if.in.dropped", netIf.Dropin, iface)) + L = append(L, CounterValue("net.if.in.fifo.errs", netIf.Fifoin, iface)) + L = append(L, CounterValue("net.if.out.bytes", netIf.BytesSent, iface)) //此处乘以8即为bit的流量 + L = append(L, CounterValue("net.if.out.packets", netIf.PacketsSent, iface)) + L = append(L, CounterValue("net.if.out.errors", netIf.Errout, iface)) + L = append(L, CounterValue("net.if.out.dropped", netIf.Dropout, iface)) + L = append(L, CounterValue("net.if.out.fifo.errs", netIf.Fifoout, iface)) + } + return +} diff --git a/funcs/iis.go b/funcs/iis.go new file mode 100644 index 0000000..9e7e293 --- /dev/null +++ b/funcs/iis.go @@ -0,0 +1,62 @@ +package funcs + +import ( + "fmt" + + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/open-falcon/common/model" +) + +func in_array_iis(a string, array []string) bool { + for _, v := range array { + if a == v { + return true + } + } + return false +} + +func iisMetrics() (L []*model.MetricValue) { + if !g.Config().IIs.Enabled { + g.Logger().Println("IIs Monitor is disabled") + return + } + websites := g.Config().IIs.Websites + + websites = append(websites, "_Total") + IIsStat, err := IIsCounters() + if err != nil { + g.Logger().Println(err) + return + } + for _, iisStat := range IIsStat { + if in_array_iis(iisStat.Name, websites) { + tag := fmt.Sprintf("site=%s", format_mertic(iisStat.Name)) + L = append(L, CounterValue("iis.bytes.received", iisStat.BytesReceivedPersec, tag)) + L = append(L, CounterValue("iis.bytes.sent", iisStat.BytesSentPersec, tag)) + L = append(L, CounterValue("iis.requests.cgi", iisStat.CGIRequestsPersec, tag)) + L = append(L, CounterValue("iis.connection.attempts", iisStat.ConnectionAttemptsPersec, tag)) + L = append(L, CounterValue("iis.requests.copy", iisStat.CopyRequestsPersec, tag)) + L = append(L, GaugeValue("iis.connections", iisStat.CurrentConnections, tag)) + L = append(L, CounterValue("iis.requests.delete", iisStat.DeleteRequestsPersec, tag)) + L = append(L, CounterValue("iis.requests.get", iisStat.GetRequestsPersec, tag)) + L = append(L, CounterValue("iis.requests.head", iisStat.HeadRequestsPersec, tag)) + L = append(L, CounterValue("iis.requests.isapi", iisStat.ISAPIExtensionRequestsPersec, tag)) + L = append(L, CounterValue("iis.errors.locked", iisStat.LockedErrorsPersec, tag)) + L = append(L, CounterValue("iis.requests.lock", iisStat.LockRequestsPersec, tag)) + L = append(L, CounterValue("iis.requests.mkcol", iisStat.MkcolRequestsPersec, tag)) + L = append(L, CounterValue("iis.requests.move", iisStat.MoveRequestsPersec, tag)) + L = append(L, CounterValue("iis.errors.notfound", iisStat.NotFoundErrorsPersec, tag)) + L = append(L, CounterValue("iis.requests.options", iisStat.OptionsRequestsPersec, tag)) + L = append(L, CounterValue("iis.requests.post", iisStat.PostRequestsPersec, tag)) + L = append(L, CounterValue("iis.requests.propfind", iisStat.PropfindRequestsPersec, tag)) + L = append(L, CounterValue("iis.requests.proppatch", iisStat.ProppatchRequestsPersec, tag)) + L = append(L, CounterValue("iis.requests.put", iisStat.PutRequestsPersec, tag)) + L = append(L, CounterValue("iis.requests.search", iisStat.SearchRequestsPersec, tag)) + L = append(L, CounterValue("iis.requests.trace", iisStat.TraceRequestsPersec, tag)) + L = append(L, CounterValue("iis.requests.unlock", iisStat.UnlockRequestsPersec, tag)) + L = append(L, GaugeValue("iis.service.uptime", iisStat.ServiceUptime, tag)) + } + } + return +} diff --git a/funcs/iiswmi.go b/funcs/iiswmi.go new file mode 100644 index 0000000..a120394 --- /dev/null +++ b/funcs/iiswmi.go @@ -0,0 +1,42 @@ +package funcs + +import ( + "github.com/StackExchange/wmi" +) + +type Win32_PerfRawData_W3SVC_WebService struct { + BytesReceivedPersec uint64 + BytesSentPersec uint64 + CGIRequestsPersec uint32 + ConnectionAttemptsPersec uint32 + CopyRequestsPersec uint32 + CurrentConnections uint32 + DeleteRequestsPersec uint32 + GetRequestsPersec uint32 + HeadRequestsPersec uint32 + ISAPIExtensionRequestsPersec uint32 + LockRequestsPersec uint32 + LockedErrorsPersec uint32 + MkcolRequestsPersec uint32 + MoveRequestsPersec uint32 + Name string + NotFoundErrorsPersec uint32 + OptionsRequestsPersec uint32 + PostRequestsPersec uint32 + PropfindRequestsPersec uint32 + ProppatchRequestsPersec uint32 + PutRequestsPersec uint32 + SearchRequestsPersec uint32 + TraceRequestsPersec uint32 + UnlockRequestsPersec uint32 + ServiceUptime uint32 +} + +func IIsCounters() ([]Win32_PerfRawData_W3SVC_WebService, error) { + var dst []Win32_PerfRawData_W3SVC_WebService + err := wmi.Query("SELECT * FROM Win32_PerfRawData_W3SVC_WebService", &dst) + if err != nil { + return dst, err + } + return dst, nil +} diff --git a/funcs/meminfo.go b/funcs/meminfo.go new file mode 100644 index 0000000..7f0c836 --- /dev/null +++ b/funcs/meminfo.go @@ -0,0 +1,35 @@ +package funcs + +import ( + "github.com/freedomkk-qfeng/windows-agent/g" + + "github.com/open-falcon/common/model" + "github.com/shirou/gopsutil/mem" +) + +func mem_info() (*mem.VirtualMemoryStat, error) { + meminfo, err := mem.VirtualMemory() + return meminfo, err +} + +func MemMetrics() []*model.MetricValue { + meminfo, err := mem_info() + if err != nil { + g.Logger().Println(err) + return []*model.MetricValue{} + } + memTotal := meminfo.Total + memUsed := meminfo.Used + memFree := meminfo.Available + pmemUsed := 100 * float64(memUsed) / float64(memTotal) + pmemFree := 100 * float64(memFree) / float64(memTotal) + + return []*model.MetricValue{ + GaugeValue("mem.memtotal", memTotal), + GaugeValue("mem.memused", memUsed), + GaugeValue("mem.memfree", memFree), + GaugeValue("mem.memfree.percent", pmemFree), + GaugeValue("mem.memused.percent", pmemUsed), + } + +} diff --git a/funcs/metric.go b/funcs/metric.go new file mode 100644 index 0000000..664a18d --- /dev/null +++ b/funcs/metric.go @@ -0,0 +1,16 @@ +package funcs + +var MsSQL_Mertics_instance = map[string]string{ + "Lock_Waits/sec": "GUAGE", + "Average_Wait_Time_(ms)": "GUAGE", + "Log_File(s)_Size_(KB)": "GUAGE", + "Log_File(s)_Used_Size_(KB)": "GUAGE", + "Percent_Log_Used": "GUAGE", +} + +var MsSQL_Mertics = map[string]string{ + "Errors/sec": "GUAGE", + "Target_Server_Memory_(KB)": "GUAGE", + "Total_Server_Memory_(KB)": "GUAGE", + "Batch_Requests/sec": "GUAGE", +} diff --git a/funcs/mssql.go b/funcs/mssql.go new file mode 100644 index 0000000..663732c --- /dev/null +++ b/funcs/mssql.go @@ -0,0 +1,263 @@ +package funcs + +import ( + "database/sql" + "fmt" + "log" + "strings" + + _ "github.com/denisenkom/go-mssqldb" + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/open-falcon/common/model" +) + +type mssql struct { + metric string + value float64 + Type string + Tag string +} + +func mssqlMetrics() (L []*model.MetricValue) { + if !g.Config().MsSQL.Enabled { + g.Logger().Println("MsSQL Monitor is disabled") + return + } + + server := g.Config().MsSQL.Addr + port := g.Config().MsSQL.Port + user := g.Config().MsSQL.Username + password := g.Config().MsSQL.Password + instance := g.Config().MsSQL.Instance + encrypt := g.Config().MsSQL.Encrypt + instance = append(instance, "_Total") + + db, err := mssql_conn(server, port, user, password, encrypt) + if err != nil { + log.Println(err) + return + } + defer db.Close() + + uptime, err := uptime_query(db) + if err == nil { + L = append(L, GaugeValue("MsSQL.Uptime", uptime)) + } else { + g.Logger().Println(err) + } + conn, err := conn_query(db) + if err == nil { + L = append(L, GaugeValue("MsSQL.Connection", conn)) + } else { + g.Logger().Println(err) + } + io_req, err := io_req_query(db) + if err == nil { + L = append(L, GaugeValue("MsSQL.IO_requests", io_req)) + } else { + g.Logger().Println(err) + } + mssql_performance, err := performance_query(db, instance) + if err == nil { + for _, perf := range mssql_performance { + switch perf.Type { + case "GUAGE": + L = append(L, GaugeValue("MsSQL."+perf.metric, perf.value, perf.Tag)) + case "COUNTER": + L = append(L, GaugeValue("MsSQL."+perf.metric, perf.value, perf.Tag)) + } + } + } else { + g.Logger().Println(err) + } + return +} + +func mssql_conn(server string, port int, user string, password string, encrypt string) (*sql.DB, error) { + connString := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%d;encrypt=%s", server, user, password, port, encrypt) + db, err := sql.Open("mssql", connString) + if err != nil { + return nil, err + } + return db, err + +} + +func uptime_query(db *sql.DB) (int64, error) { + sql := "select DATEDIFF(second,(select crdate from master.dbo.sysdatabases where NAME = 'tempdb'),GETDATE())" + rows, err := db.Query(sql) + if err != nil { + return 0, err + } + defer rows.Close() + cols, err := rows.Columns() + if err != nil { + return 0, err + } + if cols == nil { + return 0, err + } + vals := make([]interface{}, len(cols)) + for i := 0; i < len(cols); i++ { + vals[i] = new(interface{}) + } + var uptime int64 + for rows.Next() { + err = rows.Scan(vals...) + if err != nil { + g.Logger().Println(err) + continue + } + v := vals[0].(*interface{}) + uptime = (*v).(int64) + + } + + return uptime, nil +} + +func io_req_query(db *sql.DB) (float64, error) { + sql := "select count(*) as io_req from sys.dm_io_pending_io_requests" + rows, err := db.Query(sql) + if err != nil { + return 0, err + } + defer rows.Close() + cols, err := rows.Columns() + if err != nil { + return 0, err + } + if cols == nil { + return 0, err + } + vals := make([]interface{}, len(cols)) + for i := 0; i < len(cols); i++ { + vals[i] = new(interface{}) + } + var value float64 + for rows.Next() { + err = rows.Scan(vals...) + if err != nil { + g.Logger().Println(err) + continue + } + v := vals[0].(*interface{}) + vv := (*v).(int64) + value = float64(vv) + } + return value, nil +} + +func conn_query(db *sql.DB) (float64, error) { + sql := "SELECT COUNT(*) AS CONNECTIONS FROM sys.dm_exec_connections" + rows, err := db.Query(sql) + if err != nil { + return 0, err + } + defer rows.Close() + cols, err := rows.Columns() + if err != nil { + return 0, err + } + if cols == nil { + return 0, err + } + vals := make([]interface{}, len(cols)) + for i := 0; i < len(cols); i++ { + vals[i] = new(interface{}) + } + var value float64 + for rows.Next() { + err = rows.Scan(vals...) + if err != nil { + g.Logger().Println(err) + continue + } + v := vals[0].(*interface{}) + vv := (*v).(int64) + value = float64(vv) + } + return value, nil +} + +func performance_query(db *sql.DB, instance []string) ([]mssql, error) { + sql := "select * from sys.dm_os_performance_counters" + rows, err := db.Query(sql) + if err != nil { + return nil, err + } + defer rows.Close() + cols, err := rows.Columns() + if err != nil { + return nil, err + } + if cols == nil { + return nil, err + } + vals := make([]interface{}, len(cols)) + for i := 0; i < len(cols); i++ { + vals[i] = new(interface{}) + } + var result []mssql + var result_value mssql + for rows.Next() { + err = rows.Scan(vals...) + if err != nil { + g.Logger().Println(err) + continue + } + v1 := vals[1].(*interface{}) + v2 := vals[2].(*interface{}) + v3 := vals[3].(*interface{}) + counter_name := (*v1).(string) + instance_name := (*v2).(string) + value := (*v3).(int64) + counter_name = format_mertic(counter_name) + instance_name = format_mertic(instance_name) + if Type, ok := MsSQL_Mertics_instance[counter_name]; ok { + if in_array(instance_name, instance) { + result_value.metric = counter_name + result_value.Tag = "instance=" + instance_name + result_value.Type = Type + result_value.value = float64(value) + result = append(result, result_value) + } + } + if Type, ok := MsSQL_Mertics[counter_name]; ok { + if counter_name == "Errors/sec" { + result_value.metric = counter_name + result_value.Tag = "error_type=" + instance_name + result_value.Type = Type + result_value.value = float64(value) + result = append(result, result_value) + } else { + result_value.metric = counter_name + result_value.Tag = "" + result_value.Type = Type + result_value.value = float64(value) + result = append(result, result_value) + } + + } + } + if rows.Err() != nil { + return nil, rows.Err() + } + return result, nil +} + +func format_mertic(metric string) string { + result := strings.TrimSpace(metric) + result = strings.Replace(result, " ", "_", -1) + return result +} + +func in_array(a string, array []string) bool { + for _, v := range array { + v = format_mertic(v) + if a == v { + return true + } + } + return false +} diff --git a/funcs/portstat.go b/funcs/portstat.go new file mode 100644 index 0000000..b04b3cc --- /dev/null +++ b/funcs/portstat.go @@ -0,0 +1,48 @@ +package funcs + +import ( + "fmt" + + "net" + + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/open-falcon/common/model" +) + +const ( + minTCPPort = 0 + maxTCPPort = 65535 +) + +func IsTCPPortUsed(port int64) bool { + if port < minTCPPort || port > maxTCPPort { + return false + } + + conn, err := net.Listen("tcp", ":"+string(port)) + + if err != nil { + return true + } + conn.Close() + return false +} + +func PortMetrics() (L []*model.MetricValue) { + reportPorts := g.ReportPorts() + sz := len(reportPorts) + if sz == 0 { + return + } + + for i := 0; i < sz; i++ { + tags := fmt.Sprintf("port=%d", reportPorts[i]) + if IsTCPPortUsed(reportPorts[i]) { + L = append(L, GaugeValue(g.NET_PORT_LISTEN, 1, tags)) + } else { + L = append(L, GaugeValue(g.NET_PORT_LISTEN, 0, tags)) + } + } + + return +} diff --git a/funcs/process.go b/funcs/process.go new file mode 100644 index 0000000..32f61b7 --- /dev/null +++ b/funcs/process.go @@ -0,0 +1,86 @@ +package funcs + +import ( + "strings" + "time" + + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/open-falcon/common/model" + "github.com/shirou/gopsutil/process" +) + +type P struct { + name string + cmdline string +} + +func ProcMetrics() (L []*model.MetricValue) { + + reportProcs := g.ReportProcs() + sz := len(reportProcs) + if sz == 0 { + return + } + startTime := time.Now() + ps, err := Processes() + if err != nil { + g.Logger().Println(err) + return + } + + pslen := len(ps) + + for tags, m := range reportProcs { + cnt := 0 + for i := 0; i < pslen; i++ { + if is_a(ps[i], m) { + cnt++ + } + } + + L = append(L, GaugeValue(g.PROC_NUM, cnt, tags)) + } + endTime := time.Now() + g.Logger().Printf("UpdateProcessStats complete. Process time %s. Number of Process is %d", endTime.Sub(startTime), pslen) + return +} + +func is_a(p P, m map[int]string) bool { + // only one kv pair + for key, val := range m { + if key == 1 { + // name + if val != p.name { + return false + } + } else if key == 2 { + // cmdline + if !strings.Contains(p.cmdline, val) { + return false + } + } + } + return true +} + +func Processes() ([]P, error) { + var processes = []P{} + var PROCESS P + pids, err := process.Pids() + if err != nil { + return processes, err + } + for _, pid := range pids { + p, err := process.NewProcess(pid) + if err == nil { + pname, err := p.Name() + pcmdline, err := p.Cmdline() + if err == nil { + PROCESS.name = pname + PROCESS.cmdline = pcmdline + processes = append(processes, PROCESS) + } + } + } + return processes, err +} diff --git a/funcs/tcpstat.go b/funcs/tcpstat.go new file mode 100644 index 0000000..89bb0f9 --- /dev/null +++ b/funcs/tcpstat.go @@ -0,0 +1,63 @@ +package funcs + +import ( + "log" + + "github.com/StackExchange/wmi" + "github.com/open-falcon/common/model" +) + +type Tcpipdatastat struct { + ConFailures uint64 `json:"confailures"` + ConActive uint64 `json:"conactive"` + ConPassive uint64 `json:"conpassive"` + ConEstablished uint64 `json:"conestablished"` + ConReset uint64 `json:"conreset"` +} + +type Win32_TCPPerfFormattedData struct { + ConnectionFailures uint64 + ConnectionsActive uint64 + ConnectionsPassive uint64 + ConnectionsEstablished uint64 + ConnectionsReset uint64 +} + +func TcpipMetrics() (L []*model.MetricValue) { + + ds, err := TcpipCounters() + if err != nil { + log.Println("Get tcpip data fail: ", err) + return + } + + L = append(L, CounterValue("tcpip.confailures", ds[0].ConFailures)) + L = append(L, CounterValue("tcpip.conactive", ds[0].ConActive)) + L = append(L, CounterValue("tcpip.conpassive", ds[0].ConPassive)) + L = append(L, GaugeValue("tcpip.conestablished", ds[0].ConEstablished)) + L = append(L, CounterValue("tcpip.conreset", ds[0].ConReset)) + + return +} + +func TcpipCounters() ([]Tcpipdatastat, error) { + ret := make([]Tcpipdatastat, 0) + var dst []Win32_TCPPerfFormattedData + err := wmi.Query("SELECT ConnectionFailures,ConnectionsActive,ConnectionsPassive,ConnectionsEstablished,ConnectionsReset FROM Win32_PerfRawData_Tcpip_TCPv4", &dst) + if err != nil { + return ret, err + } + + for _, d := range dst { + + ret = append(ret, Tcpipdatastat{ + ConFailures: uint64(d.ConnectionFailures), + ConActive: uint64(d.ConnectionsActive), + ConPassive: uint64(d.ConnectionsPassive), + ConEstablished: uint64(d.ConnectionsEstablished), + ConReset: uint64(d.ConnectionsReset), + }) + } + + return ret, nil +} diff --git a/g/cfg.go b/g/cfg.go new file mode 100644 index 0000000..a745c70 --- /dev/null +++ b/g/cfg.go @@ -0,0 +1,132 @@ +package g + +import ( + "encoding/json" + "log" + "os" + "sync" + + "github.com/toolkits/file" +) + +type MsSQLConfig struct { + Enabled bool `json:"enabled"` + Addr string `json:"addr"` + Port int `json:"port"` + Username string `json:"username"` + Password string `json:"password"` + Encrypt string `json:"encrypt"` + Instance []string `json:"instance"` +} + +type IIsConfig struct { + Enabled bool `json:"enabled"` + Websites []string `json:"websites"` +} + +type HeartbeatConfig struct { + Enabled bool `json:"enabled"` + Addr string `json:"addr"` + Interval int `json:"interval"` + Timeout int `json:"timeout"` +} + +type TransferConfig struct { + Enabled bool `json:"enabled"` + Addrs []string `json:"addrs"` + Interval int `json:"interval"` + Timeout int `json:"timeout"` +} + +type HttpConfig struct { + Enabled bool `json:"enabled"` + Listen string `json:"listen"` + Backdoor bool `json:"backdoor"` +} + +type CollectorConfig struct { + IfacePrefix []string `json:"ifacePrefix"` +} + +type GlobalConfig struct { + Debug bool `json:"debug"` + Hostname string `json:"hostname"` + IP string `json:"ip"` + IIs *IIsConfig `json:"iis"` + MsSQL *MsSQLConfig `json:"mssql"` + Logfile string `json:"logfile"` + Heartbeat *HeartbeatConfig `json:"heartbeat"` + Transfer *TransferConfig `json:"transfer"` + Http *HttpConfig `json:"http"` + Collector *CollectorConfig `json:"collector"` + IgnoreMetrics map[string]bool `json:"ignore"` +} + +var ( + ConfigFile string + config *GlobalConfig + lock = new(sync.RWMutex) +) + +func Config() *GlobalConfig { + lock.RLock() + defer lock.RUnlock() + return config +} + +func Hostname() (string, error) { + hostname := Config().Hostname + if hostname != "" { + return hostname, nil + } + + hostname, err := os.Hostname() + if err != nil { + logger.Println("ERROR: os.Hostname() fail", err) + } + return hostname, err +} + +func IP() string { + ip := Config().IP + if ip != "" { + // use ip in configuration + return ip + } + + if len(LocalIps) > 0 { + ip = LocalIps[0] + } + + return ip +} + +func ParseConfig(cfg string) { + if cfg == "" { + log.Fatalln("use -c to specify configuration file") + } + + if !file.IsExist(cfg) { + log.Fatalln("config file:", cfg, "is not existent. maybe you need `mv cfg.example.json cfg.json`") + } + + ConfigFile = cfg + + configContent, err := file.ToTrimString(cfg) + if err != nil { + log.Fatalln("read config file:", cfg, "fail:", err) + } + + var c GlobalConfig + err = json.Unmarshal([]byte(configContent), &c) + if err != nil { + log.Fatalln("parse config file:", cfg, "fail:", err) + } + + lock.Lock() + defer lock.Unlock() + + config = &c + + log.Println("read config file:", cfg, "successfully") +} diff --git a/g/const.go b/g/const.go new file mode 100644 index 0000000..b6d412b --- /dev/null +++ b/g/const.go @@ -0,0 +1,15 @@ +package g + +import ( + "time" +) + +// changelog: +// 1.0.0 windows-agent +const ( + VERSION = "1.0.0" + COLLECT_INTERVAL = time.Second + NET_PORT_LISTEN = "net.port.listen" + DU_BS = "du.bs" + PROC_NUM = "proc.num" +) diff --git a/g/g.go b/g/g.go new file mode 100644 index 0000000..ac5a38a --- /dev/null +++ b/g/g.go @@ -0,0 +1,11 @@ +package g + +import ( + "log" + "runtime" +) + +func init() { + runtime.GOMAXPROCS(runtime.NumCPU()) + log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) +} diff --git a/g/rpc.go b/g/rpc.go new file mode 100644 index 0000000..f0a2450 --- /dev/null +++ b/g/rpc.go @@ -0,0 +1,83 @@ +package g + +import ( + "math" + "net/rpc" + "sync" + "time" + + "github.com/toolkits/net" +) + +type SingleConnRpcClient struct { + sync.Mutex + rpcClient *rpc.Client + RpcServer string + Timeout time.Duration +} + +func (this *SingleConnRpcClient) close() { + if this.rpcClient != nil { + this.rpcClient.Close() + this.rpcClient = nil + } +} + +func (this *SingleConnRpcClient) insureConn() { + if this.rpcClient != nil { + return + } + + var err error + var retry int = 1 + + for { + if this.rpcClient != nil { + return + } + + this.rpcClient, err = net.JsonRpcClient("tcp", this.RpcServer, this.Timeout) + if err == nil { + return + } + + logger.Printf("dial %s fail: %v", this.RpcServer, err) + + if retry > 6 { + retry = 1 + } + + time.Sleep(time.Duration(math.Pow(2.0, float64(retry))) * time.Second) + + retry++ + } +} + +func (this *SingleConnRpcClient) Call(method string, args interface{}, reply interface{}) error { + + this.Lock() + defer this.Unlock() + + this.insureConn() + + timeout := time.Duration(50 * time.Second) + done := make(chan error) + + go func() { + err := this.rpcClient.Call(method, args, reply) + done <- err + }() + + select { + case <-time.After(timeout): + logger.Printf("[WARN] rpc call timeout %v => %v", this.rpcClient, this.RpcServer) + this.close() + case err := <-done: + if err != nil { + this.close() + return err + } + } + + return nil +} diff --git a/g/transfer.go b/g/transfer.go new file mode 100644 index 0000000..ab8ee7d --- /dev/null +++ b/g/transfer.go @@ -0,0 +1,47 @@ +package g + +import ( + "math/rand" + "sync" + "time" + + "github.com/open-falcon/common/model" +) + +var ( + TransferClientsLock *sync.RWMutex = new(sync.RWMutex) + TransferClients map[string]*SingleConnRpcClient = map[string]*SingleConnRpcClient{} +) + +func SendMetrics(metrics []*model.MetricValue, resp *model.TransferResponse) { + rand.Seed(time.Now().UnixNano()) + for _, i := range rand.Perm(len(Config().Transfer.Addrs)) { + addr := Config().Transfer.Addrs[i] + if _, ok := TransferClients[addr]; !ok { + initTransferClient(addr) + } + if updateMetrics(addr, metrics, resp) { + break + } + } +} + +func initTransferClient(addr string) { + TransferClientsLock.Lock() + defer TransferClientsLock.Unlock() + TransferClients[addr] = &SingleConnRpcClient{ + RpcServer: addr, + Timeout: time.Duration(Config().Transfer.Timeout) * time.Millisecond, + } +} + +func updateMetrics(addr string, metrics []*model.MetricValue, resp *model.TransferResponse) bool { + TransferClientsLock.RLock() + defer TransferClientsLock.RUnlock() + err := TransferClients[addr].Call("Transfer.Update", metrics, resp) + if err != nil { + logger.Println("call Transfer.Update fail", addr, err) + return false + } + return true +} diff --git a/g/var.go b/g/var.go new file mode 100644 index 0000000..dfe4bdb --- /dev/null +++ b/g/var.go @@ -0,0 +1,187 @@ +package g + +import ( + "log" + "os" + "strings" + "sync" + "time" + + "github.com/open-falcon/common/model" + "github.com/toolkits/net" + "github.com/toolkits/slice" +) + +var ( + Root string + logger *log.Logger +) + +func InitRootDir() { + var err error + Root, err = os.Getwd() + if err != nil { + log.Fatalln("getwd fail:", err) + } +} + +func InitLog() { + fileName := Config().Logfile + logFile, err := os.Create(fileName) + if err != nil { + log.Fatalln("open file error !") + } + logger = log.New(logFile, "[Debug]", log.LstdFlags) + log.Println("logging on", fileName) +} + +func Logger() *log.Logger { + lock.RLock() + defer lock.RUnlock() + return logger +} + +var LocalIps []string + +func InitLocalIps() { + var err error + LocalIps, err = net.IntranetIP() + if err != nil { + logger.Fatalln("get intranet ip fail:", err) + } +} + +var ( + HbsClient *SingleConnRpcClient +) + +func InitRpcClients() { + if Config().Heartbeat.Enabled { + HbsClient = &SingleConnRpcClient{ + RpcServer: Config().Heartbeat.Addr, + Timeout: time.Duration(Config().Heartbeat.Timeout) * time.Millisecond, + } + } +} + +func SendToTransfer(metrics []*model.MetricValue) { + if len(metrics) == 0 { + return + } + + debug := Config().Debug + + if debug { + logger.Printf("=> %v\n", len(metrics), metrics[0]) + } + + var resp model.TransferResponse + SendMetrics(metrics, &resp) + + if debug { + logger.Println("<=", &resp) + } +} + +var ( + reportUrls map[string]string + reportUrlsLock = new(sync.RWMutex) +) + +func ReportUrls() map[string]string { + reportUrlsLock.RLock() + defer reportUrlsLock.RUnlock() + return reportUrls +} + +func SetReportUrls(urls map[string]string) { + reportUrlsLock.RLock() + defer reportUrlsLock.RUnlock() + reportUrls = urls +} + +var ( + reportPorts []int64 + reportPortsLock = new(sync.RWMutex) +) + +func ReportPorts() []int64 { + reportPortsLock.RLock() + defer reportPortsLock.RUnlock() + return reportPorts +} + +func SetReportPorts(ports []int64) { + reportPortsLock.Lock() + defer reportPortsLock.Unlock() + reportPorts = ports +} + +var ( + duPaths []string + duPathsLock = new(sync.RWMutex) +) + +func DuPaths() []string { + duPathsLock.RLock() + defer duPathsLock.RUnlock() + return duPaths +} + +func SetDuPaths(paths []string) { + duPathsLock.Lock() + defer duPathsLock.Unlock() + duPaths = paths +} + +var ( + // tags => {1=>name, 2=>cmdline} + // e.g. 'name=falcon-agent'=>{1=>falcon-agent} + // e.g. 'cmdline=xx'=>{2=>xx} + reportProcs map[string]map[int]string + reportProcsLock = new(sync.RWMutex) +) + +func ReportProcs() map[string]map[int]string { + reportProcsLock.RLock() + defer reportProcsLock.RUnlock() + return reportProcs +} + +func SetReportProcs(procs map[string]map[int]string) { + reportProcsLock.Lock() + defer reportProcsLock.Unlock() + reportProcs = procs +} + +var ( + ips []string + ipsLock = new(sync.Mutex) +) + +func TrustableIps() []string { + ipsLock.Lock() + defer ipsLock.Unlock() + return ips +} + +func SetTrustableIps(ipStr string) { + arr := strings.Split(ipStr, ",") + ipsLock.Lock() + defer ipsLock.Unlock() + ips = arr +} + +func IsTrustable(remoteAddr string) bool { + ip := remoteAddr + idx := strings.LastIndex(remoteAddr, ":") + if idx > 0 { + ip = remoteAddr[0:idx] + } + + if ip == "127.0.0.1" { + return true + } + + return slice.ContainsString(TrustableIps(), ip) +} diff --git a/http/admin.go b/http/admin.go new file mode 100644 index 0000000..1476764 --- /dev/null +++ b/http/admin.go @@ -0,0 +1,19 @@ +package http + +import ( + "net/http" + + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/toolkits/file" +) + +func configAdminRoutes() { + + http.HandleFunc("/workdir", func(w http.ResponseWriter, r *http.Request) { + RenderDataJson(w, file.SelfDir()) + }) + + http.HandleFunc("/ips", func(w http.ResponseWriter, r *http.Request) { + RenderDataJson(w, g.TrustableIps()) + }) +} diff --git a/http/health.go b/http/health.go new file mode 100644 index 0000000..9eeecda --- /dev/null +++ b/http/health.go @@ -0,0 +1,17 @@ +package http + +import ( + "net/http" + + "github.com/freedomkk-qfeng/windows-agent/g" +) + +func configHealthRoutes() { + http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("ok")) + }) + + http.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(g.VERSION)) + }) +} diff --git a/http/http.go b/http/http.go new file mode 100644 index 0000000..3a82514 --- /dev/null +++ b/http/http.go @@ -0,0 +1,68 @@ +package http + +import ( + "encoding/json" + + "net/http" + _ "net/http/pprof" + + "github.com/freedomkk-qfeng/windows-agent/g" +) + +type Dto struct { + Msg string `json:"msg"` + Data interface{} `json:"data"` +} + +func init() { + configAdminRoutes() + configHealthRoutes() + configPushRoutes() +} + +func RenderJson(w http.ResponseWriter, v interface{}) { + bs, err := json.Marshal(v) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.Write(bs) +} + +func RenderDataJson(w http.ResponseWriter, data interface{}) { + RenderJson(w, Dto{Msg: "success", Data: data}) +} + +func RenderMsgJson(w http.ResponseWriter, msg string) { + RenderJson(w, map[string]string{"msg": msg}) +} + +func AutoRender(w http.ResponseWriter, data interface{}, err error) { + if err != nil { + RenderMsgJson(w, err.Error()) + return + } + + RenderDataJson(w, data) +} + +func Start() { + if !g.Config().Http.Enabled { + return + } + + addr := g.Config().Http.Listen + if addr == "" { + return + } + + s := &http.Server{ + Addr: addr, + MaxHeaderBytes: 1 << 30, + } + + g.Logger().Println("listening", addr) + g.Logger().Fatalln(s.ListenAndServe()) +} diff --git a/http/push.go b/http/push.go new file mode 100644 index 0000000..6f4f5e1 --- /dev/null +++ b/http/push.go @@ -0,0 +1,29 @@ +package http + +import ( + "encoding/json" + "net/http" + + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/open-falcon/common/model" +) + +func configPushRoutes() { + http.HandleFunc("/v1/push", func(w http.ResponseWriter, req *http.Request) { + if req.ContentLength == 0 { + http.Error(w, "body is blank", http.StatusBadRequest) + return + } + + decoder := json.NewDecoder(req.Body) + var metrics []*model.MetricValue + err := decoder.Decode(&metrics) + if err != nil { + http.Error(w, "connot decode body", http.StatusBadRequest) + return + } + + g.SendToTransfer(metrics) + w.Write([]byte("success")) + }) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..8e9f2d4 --- /dev/null +++ b/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/freedomkk-qfeng/windows-agent/cron" + "github.com/freedomkk-qfeng/windows-agent/funcs" + "github.com/freedomkk-qfeng/windows-agent/g" + "github.com/freedomkk-qfeng/windows-agent/http" +) + +func main() { + cfg := flag.String("c", "cfg.json", "configuration file") + version := flag.Bool("v", false, "show version") + check := flag.Bool("check", false, "check collector") + + flag.Parse() + + if *version { + fmt.Println(g.VERSION) + os.Exit(0) + } + + if *check { + funcs.CheckCollector() + os.Exit(0) + } + + g.ParseConfig(*cfg) + g.InitLog() + + g.InitRootDir() + g.InitLocalIps() + g.InitRpcClients() + + funcs.BuildMappers() + + go cron.InitDataHistory() + + cron.ReportAgentStatus() + + cron.SyncBuiltinMetrics() + cron.SyncTrustableIps() + cron.Collect() + + go http.Start() + + select {} +}