Skip to content

Commit aca35ec

Browse files
authored
Merge pull request #11 from TomasTomecek/sensor-stats
sensor stats
2 parents 910ef1f + c8eeae9 commit aca35ec

File tree

8 files changed

+310
-14
lines changed

8 files changed

+310
-14
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
TEST_MODULE = conf net humanize load io display
1+
TEST_MODULE = conf net humanize load io display sens
22
PREFIX ?= ${DESTDIR}/usr
33
INSTALLDIR=${PREFIX}/bin
44
DESTDIR =

README.md

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ tmux-top
33

44
Monitoring information for your [tmux](https://tmux.github.io) status line.
55

6-
`tmux-top` allows you to see your:
6+
`tmux-top` allows you to see:
77

88
* load
99
* memory usage
10-
* network information
11-
* I/O
10+
* network statistics
11+
* I/O statistics
12+
* temperature
1213

1314
![tmux-top sample](https://raw.githubusercontent.com/TomasTomecek/tmux-top/master/docs/tmux_top_example.png)
1415
![tmux-top sample](https://raw.githubusercontent.com/TomasTomecek/tmux-top/master/docs/tmux_top_example2.png)
@@ -17,7 +18,9 @@ Monitoring information for your [tmux](https://tmux.github.io) status line.
1718
Installation
1819
------------
1920

20-
This tool is written in [Go](http://golang.org/). If you want to compile it, you have to [setup your Go environment](http://golang.org/doc/install) first.
21+
This tool is written in [Go](http://golang.org/). You have to compile it yourself -- there are no binaries being provided.
22+
23+
[This is how you can setup your Go environment](http://golang.org/doc/install).
2124

2225
#### Supported platforms
2326

@@ -54,12 +57,71 @@ $ sudo make install
5457
Usage
5558
-----
5659

57-
There are four subcommands at the moment:
58-
5960
1. `tmux-top load` — load of your workstation
6061
2. `tmux-top mem` — actual memry usage and total memory
6162
3. `tmux-top net` — network statistics: IP address, network interface and current bandwidth
6263
4. `tmux-top io` — I/O statistics: current reads and writes
64+
4. `tmux-top sensors` — show sensor stats (temperature)
65+
66+
67+
Sensors
68+
-------
69+
70+
With `sensors` command, I am trying to pursue a new design of `tmux-top`,
71+
utilizing [Go templates](https://golang.org/pkg/text/template/). The idea is
72+
that `tmux-top` will just gather the data and offer it to you as Go structs
73+
which you can easily utilize and display by writing a Go template. There will
74+
be a sensible default.
75+
76+
You can easily print what data is available to you:
77+
```
78+
$ tmux-top sensors --format '{{.|printf "%#v"}}'
79+
sens.SensorsStats{Devices:[]sens.DeviceStat{sens.DeviceStat{Name:"acpitz", LowValue:48, HighValue:48, Stats:[]sens.TemperatureStat{sens.TemperatureStat{Label:"", CurrentTemp:48}}}, sens.DeviceStat{Name:"", LowValue:1e+06, HighValue:-100, Stats:[]sens.TemperatureStat{}}, sens.DeviceStat{Name:"pch_wildcat_point", LowValue:48.5, HighValue:48.5, Stats:[]sens.TemperatureStat{sens.TemperatureStat{Label:"", CurrentTemp:48.5}}}, sens.DeviceStat{Name:"iwlwifi", LowValue:41, HighValue:41, Stats:[]sens.TemperatureStat{sens.TemperatureStat{Label:"", CurrentTemp:41}}}, sens.DeviceStat{Name:"coretemp", LowValue:52, HighValue:55, Stats:[]sens.TemperatureStat{sens.TemperatureStat{Label:"Package id 0", CurrentTemp:55}, sens.TemperatureStat{Label:"Core 0", CurrentTemp:52}, sens.TemperatureStat{Label:"Core 1", CurrentTemp:55}}}}}
80+
```
81+
82+
We can see, there is a struct `SensorsStats` and it contains array of structs
83+
`DeviceStat`. Let's see what sensors are available:
84+
```
85+
$ tmux-top sensors --format '{{range $i, $device := .Devices}}{{.Name}}: {{.|printf "%#v\n"}}{{end}}'
86+
acpitz: sens.DeviceStat{Name:"acpitz", LowValue:45, HighValue:45, Stats:[]sens.TemperatureStat{sens.TemperatureStat{Label:"", CurrentTemp:45}}}
87+
: sens.DeviceStat{Name:"", LowValue:1e+06, HighValue:-100, Stats:[]sens.TemperatureStat{}}
88+
pch_wildcat_point: sens.DeviceStat{Name:"pch_wildcat_point", LowValue:48, HighValue:48, Stats:[]sens.TemperatureStat{sens.TemperatureStat{Label:"", CurrentTemp:48}}}
89+
iwlwifi: sens.DeviceStat{Name:"iwlwifi", LowValue:42, HighValue:42, Stats:[]sens.TemperatureStat{sens.TemperatureStat{Label:"", CurrentTemp:42}}}
90+
coretemp: sens.DeviceStat{Name:"coretemp", LowValue:46, HighValue:46, Stats:[]sens.TemperatureStat{sens.TemperatureStat{Label:"Package id 0", CurrentTemp:46}, sens.TemperatureStat{Label:"Core 0", CurrentTemp:46}, sens.TemperatureStat{Label:"Core 1", CurrentTemp:46}}}
91+
```
92+
93+
Each `DeviceStat` has fields `Name` (might not be populated though`, `LowValue`,
94+
`HighValue` (so you do conditions) and array of actual values, available as
95+
`Stats`. Let's see a full example:
96+
```
97+
$ tmux-top sensors --format '{{range $i, $device := .Devices}}{{.Name}}: {{range $j, $stat := .Stats}}{{.CurrentTemp}} {{end}}{{printf "\n"}}{{end}}'
98+
acpitz: 44
99+
:
100+
pch_wildcat_point: 46.5
101+
iwlwifi: 42
102+
coretemp: 45 45 44
103+
```
104+
105+
The default is:
106+
```
107+
$ tmux-top sensors --format '{{range $i, $device := .Devices}}{{if eq .Name "coretemp"}}{{if gt $device.HighValue 50.0}}Temp: {{range $j, $e := $device.Stats}}{{$e.CurrentTemp}} {{end}}{{end}}{{end}}{{end}}'
108+
Temp: 67 67 67
109+
```
110+
111+
It prints temperature if it's higher than 50 °C and it select only sensor on CPU.
112+
113+
There is also one helper function available to print values in tmux syntax:
114+
```
115+
{{tmux_display "default" "colour14" .CurrentTemp}}
116+
```
117+
118+
which would yield
119+
```
120+
#[bg=default,fg=colour14]65#[bg=default,fg=default]
121+
```
122+
123+
all of the data is coming from `/sys/class/hwmon/*`.
124+
63125

64126
Configuration
65127
-------------

cmd/tmux-top/cli.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/TomasTomecek/tmux-top/load"
99
"github.com/TomasTomecek/tmux-top/mem"
1010
"github.com/TomasTomecek/tmux-top/net"
11+
"github.com/TomasTomecek/tmux-top/sens"
1112
"github.com/urfave/cli"
1213
"os"
1314
)
@@ -98,6 +99,11 @@ func print_io(*cli.Context) {
9899
}
99100
}
100101

102+
func print_sens(ctx *cli.Context) {
103+
template := c.GetSensorsTemplate(ctx.String("format"))
104+
sens.PrintSensorStats(template)
105+
}
106+
101107
func main() {
102108
app := cli.NewApp()
103109
app.Version = "0.0.4"
@@ -128,6 +134,18 @@ func main() {
128134
Usage: "show I/O stats ",
129135
Action: print_io,
130136
},
137+
{
138+
Name: "sensors",
139+
ShortName: "s",
140+
Usage: "show sensor stats (temperature)",
141+
Action: print_sens,
142+
Flags: []cli.Flag{
143+
cli.StringFlag{
144+
Name: "format, f",
145+
Usage: "Format the output using the given Go template",
146+
},
147+
},
148+
},
131149
}
132150

133151
app.Run(os.Args)

conf/conf.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,21 @@ type MemConfiguration struct {
8888
TotalFg *string `json:"total_fg"`
8989
}
9090

91+
// configuration in a config file
92+
type SensorsConfiguration struct {
93+
Template *string `json:"template"`
94+
}
95+
9196
type LoadConfiguration struct {
9297
Intervals *[]IntervalDisplay `json:"intervals"`
9398
}
9499

95100
type Configuration struct {
96-
Load *LoadConfiguration `json:"load"`
97-
Net *NetConfiguration `json:"net"`
98-
Mem *MemConfiguration `json:"mem"`
99-
IO *IOConfiguration `json:"io"`
101+
Load *LoadConfiguration `json:"load"`
102+
Net *NetConfiguration `json:"net"`
103+
Mem *MemConfiguration `json:"mem"`
104+
IO *IOConfiguration `json:"io"`
105+
Sensors *SensorsConfiguration `json:"sensors"`
100106
}
101107

102108
func loadConfFromFile(path string) []byte {
@@ -107,7 +113,7 @@ func loadConfFromFile(path string) []byte {
107113
return response
108114
}
109115

110-
func loadConfFromBytes(json_input []byte) *Configuration {
116+
func LoadConfFromBytes(json_input []byte) *Configuration {
111117
var conf *Configuration = new(Configuration)
112118
err := json.Unmarshal(json_input, &conf)
113119

@@ -123,8 +129,8 @@ func LoadConf() *ConfigurationManager {
123129
home_dir := os.Getenv("HOME")
124130
bytes := loadConfFromFile(path.Join(home_dir, ".tmux-top"))
125131
if len(bytes) > 0 {
126-
c.User = loadConfFromBytes(bytes)
132+
c.User = LoadConfFromBytes(bytes)
127133
}
128-
c.Default = loadConfFromBytes([]byte(default_conf))
134+
c.Default = LoadConfFromBytes([]byte(default_conf))
129135
return c
130136
}

conf/default_json.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,5 +164,12 @@ var default_conf string = `
164164
"bg_color": "colour1",
165165
"fg_color": "white"
166166
}]
167+
},
168+
"sensors": {
169+
"template": "{{range $i, $device := .Devices}}{{if eq .Name \"coretemp\"}}{{if gt $device.HighValue 50.0}}Temp: {{range $j, $e := $device.Stats}}{{$e.CurrentTemp}} {{end}}{{end}}{{end}}{{end}}"
167170
}
168171
}`
172+
173+
func GetDefaultConf() string {
174+
return default_conf
175+
}

conf/manager.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package conf
22

3+
import (
4+
"fmt"
5+
"strings"
6+
"text/template"
7+
)
8+
39
/*
410
This ugly boilerplate code enables user to override default settings
511
@@ -266,3 +272,43 @@ func (c *ConfigurationManager) GetIOWriteLabelFg() string {
266272
}
267273
return *c.Default.IO.WriteLabelFg
268274
}
275+
276+
func replace(input, from, to string) string {
277+
return strings.Replace(input, from, to, -1)
278+
}
279+
280+
func tmux_display(bg, fg string, value interface{}) (response string) {
281+
response = fmt.Sprintf("#[bg=%s,fg=%s]%v#[bg=default,fg=default]", bg, fg, value)
282+
return
283+
}
284+
285+
func Init_template() template.Template {
286+
tmpl := template.New("")
287+
funcMap := template.FuncMap{
288+
"replace": replace,
289+
"tmux_display": tmux_display,
290+
}
291+
tmpl.Funcs(funcMap)
292+
return *tmpl
293+
}
294+
295+
// format -- CLI option
296+
func (c *ConfigurationManager) GetSensorsTemplate(format string) template.Template {
297+
template_s := format
298+
if format == "" {
299+
template_s = *c.Default.Sensors.Template
300+
if c.User != nil {
301+
if c.User.Sensors != nil {
302+
if c.User.Sensors.Template != nil {
303+
template_s = *c.User.Sensors.Template
304+
}
305+
}
306+
}
307+
}
308+
template := Init_template()
309+
t, err := template.Parse(template_s)
310+
if err != nil {
311+
panic(err)
312+
}
313+
return *t
314+
}

sens/sens.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
docs: `strace sensors`
3+
*/
4+
5+
package sens
6+
7+
import (
8+
"fmt"
9+
"io/ioutil"
10+
"os"
11+
"path"
12+
"path/filepath"
13+
"strconv"
14+
"strings"
15+
"text/template"
16+
)
17+
18+
var BASE_PATH string = "/sys/class/hwmon/"
19+
20+
type TemperatureStat struct {
21+
Label string
22+
CurrentTemp float64
23+
// MaxTemp float64
24+
// CritTemp float64
25+
// CritAlarmTemp float64
26+
}
27+
28+
// a list of stats for this device
29+
type DeviceStat struct {
30+
Name string `json:"Name"`
31+
LowValue float64 `json:"LowValue"`
32+
HighValue float64 `json:"HighValue"`
33+
Stats []TemperatureStat `json:"Stats"`
34+
}
35+
36+
type SensorsStats struct {
37+
Devices []DeviceStat
38+
}
39+
40+
func getFileContentToInt(p string) (float64, error) {
41+
fd, err := ioutil.ReadFile(p)
42+
if err != nil {
43+
return 0.0, err
44+
}
45+
i, err := strconv.ParseInt(strings.TrimSpace(string(fd)), 10, 64)
46+
if err != nil {
47+
return 0.0, err
48+
}
49+
return float64(i) / 1000, nil
50+
}
51+
52+
func getDeviceStats(f os.FileInfo) DeviceStat {
53+
sens_stats := make([]TemperatureStat, 0)
54+
d := DeviceStat{
55+
Stats: sens_stats,
56+
Name: "",
57+
LowValue: 1000000.0,
58+
HighValue: -100.0,
59+
}
60+
device_path, err := filepath.EvalSymlinks(path.Join(BASE_PATH, f.Name()))
61+
if err != nil {
62+
return d
63+
}
64+
65+
name_f, err := ioutil.ReadFile(path.Join(device_path, "name"))
66+
name := ""
67+
if err == nil {
68+
name = strings.TrimSpace(string(name_f))
69+
}
70+
d.Name = name
71+
72+
count := 1
73+
for {
74+
cur_p := path.Join(
75+
device_path,
76+
fmt.Sprintf("temp%s_input", strconv.Itoa(count)),
77+
)
78+
cur_f, err := getFileContentToInt(cur_p)
79+
if err != nil {
80+
break
81+
}
82+
83+
label_f, err := ioutil.ReadFile(path.Join(
84+
device_path,
85+
fmt.Sprintf("temp%s_label", strconv.Itoa(count)),
86+
))
87+
label := ""
88+
if err == nil {
89+
label = strings.TrimSpace(string(label_f))
90+
}
91+
92+
if d.LowValue > cur_f {
93+
d.LowValue = cur_f
94+
}
95+
if d.HighValue < cur_f {
96+
d.HighValue = cur_f
97+
}
98+
99+
s := TemperatureStat{
100+
CurrentTemp: cur_f,
101+
Label: label,
102+
}
103+
sens_stats = append(sens_stats, s)
104+
105+
count++
106+
}
107+
d.Stats = sens_stats
108+
return d
109+
}
110+
111+
func getSensorsStats() SensorsStats {
112+
device_stats := make([]DeviceStat, 0)
113+
s := SensorsStats{
114+
Devices: device_stats,
115+
}
116+
entries, err := ioutil.ReadDir(BASE_PATH)
117+
if err != nil {
118+
panic(err)
119+
}
120+
// fd, os.FileInfo
121+
for _, e := range entries {
122+
d := getDeviceStats(e)
123+
device_stats = append(device_stats, d)
124+
}
125+
s.Devices = device_stats
126+
return s
127+
}
128+
129+
func PrintSensorStats(t template.Template) {
130+
d := getSensorsStats()
131+
err := t.Execute(os.Stdout, d)
132+
if err != nil {
133+
panic(err)
134+
}
135+
}

0 commit comments

Comments
 (0)