Compare commits
No commits in common. "master" and "use_dhall" have entirely different histories.
|
@ -1,22 +1,3 @@
|
|||
{- Types to define a conky configuration.
|
||||
|
||||
Key terms:
|
||||
- widget: the components of a module (dials, bars, plots, etc)
|
||||
- module: a monitor for some aspect of the system to be displayed
|
||||
- column: a vertical arrangement of modules
|
||||
- panel: a horizontal arrangement of columns
|
||||
- layout: a horizontal arrangement of panels
|
||||
- geometry: the 'dimensions' of a widget
|
||||
- pattern: the color/gradient to be used in widgets
|
||||
|
||||
NOTE: geometry is defined on a global level and at the module level. The global
|
||||
level is defined in the theme type (see below) and applies to all widgets
|
||||
equally. Some dimensions are not defined globally and are instead delegated to
|
||||
each module to define themselves (these are not overrides so they must be
|
||||
specified, although most modules have sensible defaults hardcoded)
|
||||
|
||||
See the 'fallback' config for an example of how to use these types.
|
||||
-}
|
||||
let Vector2 = \(a : Type) -> { x : a, y : a }
|
||||
|
||||
let Point = Vector2 Natural
|
||||
|
@ -25,148 +6,54 @@ let Margin = Vector2 Natural
|
|||
|
||||
let FSPath = { name : Text, path : Text }
|
||||
|
||||
let TextGeo =
|
||||
{-
|
||||
Defines text dimensions for multiline visuals
|
||||
let TextGeo = { Type = { text_spacing : Natural }, default.text_spacing = 20 }
|
||||
|
||||
text_spacing: gap between lines of text
|
||||
-}
|
||||
{ Type = { text_spacing : Natural }, default.text_spacing = 20 }
|
||||
let SepGeo = { Type = { sep_spacing : Natural }, default.sep_spacing = 20 }
|
||||
|
||||
let SepGeo =
|
||||
{-
|
||||
Defines separator dimensions
|
||||
|
||||
sep_spacing: gap between the separator and either above or below
|
||||
-}
|
||||
{ Type = { sep_spacing : Natural }, default.sep_spacing = 20 }
|
||||
|
||||
let PlotGeo =
|
||||
{-
|
||||
Defines plot dimensions
|
||||
|
||||
sec_break: gap between the plot label and the plot itself
|
||||
height: height of the plot (without the label)
|
||||
ticks_y: number of ticks on the y axis
|
||||
-}
|
||||
let PlotGeo_ =
|
||||
{ Type = { sec_break : Natural, height : Natural, ticks_y : Natural }
|
||||
, default = { sec_break = 20, height = 56, ticks_y = 4 }
|
||||
}
|
||||
|
||||
let PlotGeo_ = { Type = { plot : PlotGeo.Type }, default.plot = PlotGeo::{=} }
|
||||
let PlotGeo = { Type = { plot : PlotGeo_.Type }, default.plot = PlotGeo_::{=} }
|
||||
|
||||
let TableGeo_ = { Type = { sec_break : Natural }, default.sec_break = 20 }
|
||||
|
||||
let TableGeo =
|
||||
{-
|
||||
Defines table dimensions
|
||||
|
||||
sec_break: spacing between header and the first table row
|
||||
-}
|
||||
{ Type = { sec_break : Natural }, default.sec_break = 20 }
|
||||
|
||||
let TableGeo_ =
|
||||
{ Type = { table : TableGeo.Type }, default.table = TableGeo::{=} }
|
||||
{ Type = { table : TableGeo_.Type }, default.table = TableGeo_::{=} }
|
||||
|
||||
let FSGeo =
|
||||
{-
|
||||
Defines Filesystem module dimensions
|
||||
|
||||
bar_spacing: spacing between percent usage bars
|
||||
bar_pad: spacing between usage bars and the left edge of the panel
|
||||
-}
|
||||
{ Type = { bar_spacing : Natural, bar_pad : Natural } //\\ SepGeo.Type
|
||||
, default = { bar_spacing = 20, bar_pad = 100 } /\ SepGeo::{=}
|
||||
}
|
||||
|
||||
let GfxGeo =
|
||||
{-
|
||||
Defines Graphics module dimensions
|
||||
|
||||
See PlotGeo, TextGeo, and SepGeo for options included here
|
||||
-}
|
||||
{ Type = SepGeo.Type //\\ PlotGeo_.Type //\\ TextGeo.Type
|
||||
, default = SepGeo::{=} /\ PlotGeo_::{=} /\ TextGeo::{=}
|
||||
{ Type = SepGeo.Type //\\ PlotGeo.Type //\\ TextGeo.Type
|
||||
, default = SepGeo::{=} /\ PlotGeo::{=} /\ TextGeo::{=}
|
||||
}
|
||||
|
||||
let MemGeo =
|
||||
{-
|
||||
Defines Memory module dimensions
|
||||
|
||||
See PlotGeo, TextGeo, and TableGeo for options included here
|
||||
-}
|
||||
{ Type = TextGeo.Type //\\ PlotGeo_.Type //\\ TableGeo_.Type
|
||||
, default = TextGeo::{=} /\ PlotGeo_::{=} /\ TableGeo_::{=}
|
||||
{ Type = TextGeo.Type //\\ PlotGeo.Type //\\ TableGeo.Type
|
||||
, default = TextGeo::{=} /\ PlotGeo::{=} /\ TableGeo::{=}
|
||||
}
|
||||
|
||||
let ProcGeo =
|
||||
{-
|
||||
Defines Processor module dimensions
|
||||
|
||||
See GfxGeo and TableGeo for options included here
|
||||
-}
|
||||
{ Type = GfxGeo.Type //\\ TableGeo_.Type
|
||||
, default = GfxGeo::{=} /\ TableGeo_::{=}
|
||||
{ Type = GfxGeo.Type //\\ TableGeo.Type
|
||||
, default = GfxGeo::{=} /\ TableGeo::{=}
|
||||
}
|
||||
|
||||
let PwrGeo =
|
||||
{-
|
||||
Defines Processor module dimensions
|
||||
|
||||
See TextGeo and PlotGeo for options included here
|
||||
-}
|
||||
{ Type = TextGeo.Type //\\ PlotGeo_.Type
|
||||
, default = TextGeo::{=} /\ PlotGeo_::{=}
|
||||
{ Type = TextGeo.Type //\\ PlotGeo.Type
|
||||
, default = TextGeo::{=} /\ PlotGeo::{=}
|
||||
}
|
||||
|
||||
let AllGeo =
|
||||
{ TextGeo, PlotGeo, TableGeo, FSGeo, GfxGeo, MemGeo, ProcGeo, PwrGeo }
|
||||
|
||||
let FileSystem =
|
||||
{-
|
||||
Defines Filesystem module configuration
|
||||
|
||||
show_smart: show SMART daemon indicator
|
||||
show_seafile: show seafile daemon indicator
|
||||
fs_paths: list of filesystem paths to monitor for percent usage
|
||||
geometry: dimensional data for this module
|
||||
-}
|
||||
{ Type =
|
||||
{ show_smart : Bool
|
||||
, show_seafile : Bool
|
||||
, fs_paths : List FSPath
|
||||
, geometry : FSGeo.Type
|
||||
}
|
||||
{ show_smart : Bool, fs_paths : List FSPath, geometry : FSGeo.Type }
|
||||
, default.geometry = FSGeo::{=}
|
||||
}
|
||||
|
||||
let GPUPower =
|
||||
{-
|
||||
Controls how the GPU will be queries so it can go to sleep when 'idle'.
|
||||
|
||||
freq_limit: only query the GPU every 'cycle_seconds' when frequency is below
|
||||
this value; this should be a sensible value representing and
|
||||
'idle' gpu state. Set to 0 to disable this check entirely.
|
||||
cycle_seconds: the number of seconds to query the GPU when below the idle
|
||||
frequency limit; note that this must be long enough to let
|
||||
the GPU go to sleep when it actually is ready to sleep, but
|
||||
not so long that the next query will miss too much activity
|
||||
in the case the GPU doesn't shut off
|
||||
-}
|
||||
{ Type = { freq_limit : Natural, cycle_seconds : Natural }
|
||||
, default = { freq_limit = 250, cycle_seconds = 10 }
|
||||
}
|
||||
|
||||
let Graphics =
|
||||
{-
|
||||
Defines Graphics module configuration
|
||||
|
||||
dev_power: sysfs path to graphics card power directory
|
||||
show_temp: show temperature in celsius
|
||||
show_clock: show clock speed
|
||||
show_gpu_util: show percent utilization
|
||||
show_mem_util: show percent memory utilized
|
||||
show_vid_util: show percent video utilized
|
||||
geometry: dimensional configuration for this module
|
||||
-}
|
||||
{ Type =
|
||||
{ dev_power : Text
|
||||
, show_temp : Bool
|
||||
|
@ -175,21 +62,11 @@ let Graphics =
|
|||
, show_mem_util : Bool
|
||||
, show_vid_util : Bool
|
||||
, geometry : GfxGeo.Type
|
||||
, power : GPUPower.Type
|
||||
}
|
||||
, default = { geometry = GfxGeo::{=}, power = GPUPower::{=} }
|
||||
, default.geometry = GfxGeo::{=}
|
||||
}
|
||||
|
||||
let Memory =
|
||||
{-
|
||||
Defines Memory module configuration
|
||||
|
||||
show_stats: show total memory gauge, cache, buffers, shared, and slab
|
||||
show_plot: show memory utilization plot
|
||||
show_swap: show swap utilization gauge (only if 'show_stats' is true)
|
||||
table_rows: top processes by memory to display in table (max 10)
|
||||
geometry: dimensional configuration for this module
|
||||
-}
|
||||
{ Type =
|
||||
{ show_stats : Bool
|
||||
, show_plot : Bool
|
||||
|
@ -201,35 +78,12 @@ let Memory =
|
|||
}
|
||||
|
||||
let Network =
|
||||
{-
|
||||
Defines Network module configuration
|
||||
|
||||
geometry: dimensional configuration for this module
|
||||
-}
|
||||
{ Type = { geometry : PlotGeo_.Type }, default.geometry = PlotGeo_::{=} }
|
||||
|
||||
let CoreGroup =
|
||||
{-
|
||||
Defines a processor group for the Processor module
|
||||
|
||||
threads: the number of threads for each core in this group (usually 1 or 2)
|
||||
rows: the number of rows over which to display the cores in this group
|
||||
padding: the spacing to the left and right of the gauges in this group
|
||||
-}
|
||||
{ threads : Natural, rows : Natural, padding : Natural }
|
||||
{ Type = { geometry : PlotGeo.Type }, default.geometry = PlotGeo::{=} }
|
||||
|
||||
let Processor =
|
||||
{-
|
||||
Defines Network module configuration
|
||||
|
||||
core_groups: a list of core groups to display
|
||||
show_stats: show frequency for each core group and HWP status
|
||||
show_plot: show percent utilization plot
|
||||
table_rows: top processes by cpu percent to display in table (max 10)
|
||||
geometry: dimensional configuration for this module
|
||||
-}
|
||||
{ Type =
|
||||
{ core_groups : List CoreGroup
|
||||
{ core_rows : Natural
|
||||
, core_padding : Natural
|
||||
, show_stats : Bool
|
||||
, show_plot : Bool
|
||||
, table_rows : Natural
|
||||
|
@ -238,69 +92,25 @@ let Processor =
|
|||
, default.geometry = ProcGeo::{=}
|
||||
}
|
||||
|
||||
let Pacman =
|
||||
{-
|
||||
Defines Pacman module configuration
|
||||
let RaplSpec = { name : Text, address : Text }
|
||||
|
||||
geometry: dimensional configuration for this module
|
||||
-}
|
||||
let Pacman =
|
||||
{ Type = { geometry : TextGeo.Type }, default.geometry = TextGeo::{=} }
|
||||
|
||||
let RaplSpec =
|
||||
{-
|
||||
Defines a RAPL endpoint to display in the Power module
|
||||
|
||||
name: nice name to display above plot
|
||||
address: sysfs address to the directory containing the 'energy_uj' file
|
||||
-}
|
||||
{ name : Text, address : Text }
|
||||
|
||||
let Power =
|
||||
{-
|
||||
Defines Power module configuration
|
||||
|
||||
battery: sysfs path to the battery to display
|
||||
rapl_specs: list of RAPL endpoints to display
|
||||
geometry: dimensional configuration for this module
|
||||
-}
|
||||
{ Type =
|
||||
{ battery : Text, rapl_specs : List RaplSpec, geometry : PwrGeo.Type }
|
||||
, default.geometry = PwrGeo::{=}
|
||||
}
|
||||
|
||||
let ReadWrite =
|
||||
{-
|
||||
Defines ReadWrite module configuration
|
||||
|
||||
geometry: dimensional configuration for this module
|
||||
-}
|
||||
{ Type = { devices : List Text, geometry : PlotGeo_.Type }
|
||||
, default.geometry = PlotGeo_::{=}
|
||||
{ Type = { devices : List Text, geometry : PlotGeo.Type }
|
||||
, default.geometry = PlotGeo::{=}
|
||||
}
|
||||
|
||||
let System =
|
||||
{-
|
||||
Defines System module configuration
|
||||
|
||||
geometry: dimensional configuration for this module
|
||||
-}
|
||||
Pacman
|
||||
|
||||
let AllModules =
|
||||
{ FileSystem
|
||||
, Graphics
|
||||
, Memory
|
||||
, Network
|
||||
, Pacman
|
||||
, Power
|
||||
, Processor
|
||||
, ReadWrite
|
||||
, System
|
||||
}
|
||||
let System = Pacman
|
||||
|
||||
let ModType =
|
||||
{- Wrapper type for each module
|
||||
-}
|
||||
< filesystem : FileSystem.Type
|
||||
| graphics : Graphics.Type
|
||||
| memory : Memory.Type
|
||||
|
@ -312,61 +122,21 @@ let ModType =
|
|||
| system : System.Type
|
||||
>
|
||||
|
||||
let Annotated =
|
||||
{- Helper to ensure yaml is formatted in a parsable manner for union types
|
||||
let Annotated = \(a : Type) -> { type : Text, data : a }
|
||||
|
||||
The intent is to make a block like:
|
||||
let Block = < Pad : Natural | Mod : Annotated ModType >
|
||||
|
||||
parent:
|
||||
type: <constructor_name>
|
||||
data: <stuff_in_the_union_type>
|
||||
-}
|
||||
\(a : Type) -> { type : Text, data : a }
|
||||
let Column_ = { blocks : List Block, width : Natural }
|
||||
|
||||
let Block =
|
||||
{- Either vertical padding or a module -}
|
||||
< Pad : Natural | Mod : Annotated ModType >
|
||||
let Column = < CPad : Natural | CCol : Column_ >
|
||||
|
||||
let Column_ =
|
||||
{- A column of modules to display
|
||||
let Panel_ = { columns : List Column, margins : Margin }
|
||||
|
||||
blocks: a list of blocks (either padding or a module)
|
||||
width: the width of the column (and consequently everything in said column)
|
||||
-}
|
||||
{ blocks : List Block, width : Natural }
|
||||
let Panel = < PPad : Natural | PPanel : Panel_ >
|
||||
|
||||
let Column =
|
||||
{- Either a column with modules or padding between columns -}
|
||||
< CPad : Natural | CCol : Column_ >
|
||||
|
||||
let Panel_ =
|
||||
{- A panel definition.
|
||||
|
||||
columns: a list of columns to show (must be at least one)
|
||||
margins: space between the panel border and the columns
|
||||
-}
|
||||
{ columns : List Column, margins : Margin }
|
||||
|
||||
let Panel =
|
||||
{- Wrapper type for either a panel or horizontal padding between panels -}
|
||||
< PPad : Natural | PPanel : Panel_ >
|
||||
|
||||
let Layout =
|
||||
{- The layout of the display
|
||||
|
||||
'anchor' is the coordinate of the upper-left corner, and 'panels' contains
|
||||
the specifications of each panel (ie modules to show).
|
||||
-}
|
||||
{ anchor : Point, panels : List Panel }
|
||||
let Layout = { anchor : Point, panels : List Panel }
|
||||
|
||||
let Sizes =
|
||||
{- The font sizes to use for various widgets
|
||||
|
||||
plot_label: the text above the plot
|
||||
table: the text in a table (headers and rows)
|
||||
header: the text of each module header
|
||||
normal: all other text (excluding plot axes)
|
||||
-}
|
||||
{ Type =
|
||||
{ normal : Natural
|
||||
, plot_label : Natural
|
||||
|
@ -377,29 +147,16 @@ let Sizes =
|
|||
}
|
||||
|
||||
let Font =
|
||||
{- A complete font specification -}
|
||||
{ Type = { family : Text, sizes : Sizes.Type }
|
||||
, default = { family = "Neuropolitical", sizes = Sizes::{=} }
|
||||
}
|
||||
|
||||
let PlotGeometry =
|
||||
{- Global dimensions for a plot
|
||||
|
||||
seconds: number of seconds to display on the x axis
|
||||
ticks_x: the number of ticks to display on the x axis
|
||||
-}
|
||||
{ Type = { seconds : Natural, ticks_x : Natural }
|
||||
, default = { seconds = 90, ticks_x = 9 }
|
||||
}
|
||||
|
||||
let TableGeometry =
|
||||
{- Global dimensions for a table
|
||||
|
||||
name_chars: the max number of characters a cell can have before being truncated
|
||||
padding: the margins between the border and the interior text
|
||||
header_padding: the distance between the header and the first row of text
|
||||
row_spacing: the distance between each row
|
||||
-}
|
||||
{ Type =
|
||||
{ name_chars : Natural
|
||||
, padding : Margin
|
||||
|
@ -415,17 +172,11 @@ let TableGeometry =
|
|||
}
|
||||
|
||||
let HeaderGeometry =
|
||||
{- Global dimensions for the header of a module
|
||||
|
||||
underline_offset: distance between bottom edge of text and the line below
|
||||
padding: the distance between the underline and the first widget in the module
|
||||
-}
|
||||
{ Type = { underline_offset : Natural, padding : Natural }
|
||||
, default = { underline_offset = 26, padding = 19 }
|
||||
}
|
||||
|
||||
let Geometry =
|
||||
{- Global config for dimensions of various widgets -}
|
||||
{ Type =
|
||||
{ plot : PlotGeometry.Type
|
||||
, table : TableGeometry.Type
|
||||
|
@ -438,72 +189,28 @@ let Geometry =
|
|||
}
|
||||
}
|
||||
|
||||
let Color =
|
||||
{- Alias for a solid color represented as a single hex number -}
|
||||
Natural
|
||||
let StopRGB = { color : Natural, stop : Double }
|
||||
|
||||
let Alpha =
|
||||
{- Alias for alpha channel as a fraction between 0 and 1 -}
|
||||
Double
|
||||
let StopRGBA = { color : Natural, stop : Double, alpha : Double }
|
||||
|
||||
let Stop =
|
||||
{- Alias for the position of a color in a gradient (between 0 and 1) -}
|
||||
Double
|
||||
|
||||
let StopRGB =
|
||||
{- A solid color with a position in a gradiant -}
|
||||
{ color : Color, stop : Stop }
|
||||
|
||||
let StopRGBA =
|
||||
{- A transparent color with a position in a gradiant -}
|
||||
{ color : Color, stop : Stop, alpha : Alpha }
|
||||
|
||||
let ColorAlpha =
|
||||
{- A solid transparent color -}
|
||||
{ color : Color, alpha : Alpha }
|
||||
let ColorAlpha = { color : Natural, alpha : Double }
|
||||
|
||||
let Pattern =
|
||||
{- Wrapper for different pattern types -}
|
||||
< RGB : Color
|
||||
< RGB : Natural
|
||||
| RGBA : ColorAlpha
|
||||
| GradientRGB : List StopRGB
|
||||
| GradientRGBA : List StopRGBA
|
||||
>
|
||||
|
||||
let annotatePattern =
|
||||
{- Helper function to ensure patterns are parsable in yaml
|
||||
|
||||
This will create entries in the yaml config like:
|
||||
|
||||
parent:
|
||||
type: <pattern_type>
|
||||
date:
|
||||
<field1>: ...
|
||||
<field2>: ...
|
||||
-}
|
||||
\(a : Pattern) ->
|
||||
{ type = showConstructor a, data = a } : Annotated Pattern
|
||||
|
||||
let mod =
|
||||
{- Helper function to ensure modules are parsable in yaml
|
||||
|
||||
This will create entries in the yaml config like:
|
||||
|
||||
parent:
|
||||
type: <module name>
|
||||
date:
|
||||
<field1>: ...
|
||||
<field2>: ...
|
||||
-}
|
||||
\(a : ModType) -> Block.Mod { type = showConstructor a, data = a }
|
||||
let mod = \(a : ModType) -> Block.Mod { type = showConstructor a, data = a }
|
||||
|
||||
let APattern = Annotated Pattern
|
||||
|
||||
let symGradient =
|
||||
{- Make a symmetric gradient between two colors.
|
||||
|
||||
c0 will be the color of either edge and c1 will be the color in the center
|
||||
-}
|
||||
\(c0 : Natural) ->
|
||||
\(c1 : Natural) ->
|
||||
annotatePattern
|
||||
|
@ -515,8 +222,6 @@ let symGradient =
|
|||
)
|
||||
|
||||
let Patterns =
|
||||
{- All patterns for a given theme
|
||||
-}
|
||||
{ Type =
|
||||
{ header : APattern
|
||||
, panel : { bg : APattern }
|
||||
|
@ -572,14 +277,6 @@ let Patterns =
|
|||
}
|
||||
|
||||
let Theme =
|
||||
{-
|
||||
Defines the theme for displaying the window
|
||||
|
||||
font: the font to use
|
||||
geometry: the global dimensions to use for each panel and widget (for
|
||||
everything not define by individual modules)
|
||||
patterns: colors and gradient definitions
|
||||
-}
|
||||
{ Type =
|
||||
{ font : Font.Type
|
||||
, geometry : Geometry.Type
|
||||
|
@ -589,29 +286,38 @@ let Theme =
|
|||
{ font = Font::{=}, geometry = Geometry::{=}, patterns = Patterns::{=} }
|
||||
}
|
||||
|
||||
let Bootstrap =
|
||||
{- Defines minimal options to start conky prior to calling any lua code.
|
||||
-}
|
||||
{ update_interval : Natural, dimensions : Point }
|
||||
let Bootstrap = { update_interval : Natural, dimensions : Point }
|
||||
|
||||
let Config =
|
||||
{- Global config type -}
|
||||
{ bootstrap : Bootstrap, theme : Theme.Type, layout : Layout }
|
||||
let Config = { bootstrap : Bootstrap, theme : Theme.Type, layout : Layout }
|
||||
|
||||
let toConfig =
|
||||
{- Helper function to generate a config -}
|
||||
\(update_interval : Natural) ->
|
||||
\(width : Natural) ->
|
||||
\(height : Natural) ->
|
||||
\(theme : Theme.Type) ->
|
||||
\(layout : Layout) ->
|
||||
{ bootstrap =
|
||||
{ update_interval, dimensions = { x = width, y = height } }
|
||||
, theme
|
||||
, layout
|
||||
\(i : Natural) ->
|
||||
\(x : Natural) ->
|
||||
\(y : Natural) ->
|
||||
\(t : Theme.Type) ->
|
||||
\(l : Layout) ->
|
||||
{ bootstrap = { update_interval = i, dimensions = { x, y } }
|
||||
, theme = t
|
||||
, layout = l
|
||||
}
|
||||
: Config
|
||||
|
||||
in { toConfig, Block, Column, ModType, Layout, Panel, FSPath, Theme, mod }
|
||||
/\ AllModules
|
||||
/\ AllGeo
|
||||
in { toConfig
|
||||
, Block
|
||||
, Column
|
||||
, ModType
|
||||
, Layout
|
||||
, Panel
|
||||
, FSPath
|
||||
, FileSystem
|
||||
, Graphics
|
||||
, Memory
|
||||
, Network
|
||||
, Pacman
|
||||
, Processor
|
||||
, Power
|
||||
, ReadWrite
|
||||
, System
|
||||
, Theme
|
||||
, mod
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
bootstrap:
|
||||
update_interval: 1
|
||||
dimensions: [1920, 1080]
|
||||
modules:
|
||||
memory:
|
||||
show_stats: false
|
||||
show_swap: false
|
||||
show_plot: true
|
||||
table_rows: 3
|
||||
processor:
|
||||
core_rows: 0
|
||||
core_padding: 0
|
||||
show_stats: false
|
||||
show_plot: true
|
||||
table_rows: 3
|
||||
|
||||
layout:
|
||||
anchor: [12, 11]
|
||||
panels:
|
||||
- columns:
|
||||
- {blocks: [network, 10, memory, 10, processor], width: 436}
|
||||
margins: [20, 10]
|
||||
|
||||
theme:
|
||||
font:
|
||||
family: Neuropolitical
|
||||
sizes:
|
||||
normal: 13
|
||||
plot_label: 8
|
||||
table: 11
|
||||
header: 15
|
||||
geometry:
|
||||
plot:
|
||||
seconds: 90
|
||||
ticks: [9, 4]
|
||||
height: 56
|
||||
spacing: 20
|
||||
table:
|
||||
name_chars: 8
|
||||
padding: [6, 15]
|
||||
header_padding: 20
|
||||
row_spacing: 13
|
||||
header:
|
||||
underline_offset: 26
|
||||
padding: 19
|
||||
patterns:
|
||||
header: 0xefefef
|
||||
panel:
|
||||
bg: {color: 0x121212, alpha: 0.7}
|
||||
text:
|
||||
active: 0xbfe1ff
|
||||
inactive: 0xc8c8c8
|
||||
critical: 0xff8282
|
||||
border: 0x888888
|
||||
plot:
|
||||
grid: 0x666666
|
||||
outline: 0x777777
|
||||
data:
|
||||
border:
|
||||
gradient:
|
||||
- {stop: 0, color: 0x003f7c}
|
||||
- {stop: 1, color: 0x1e90ff}
|
||||
fill:
|
||||
gradient_alpha:
|
||||
- {stop: 0.2, color: 0x316ece, alpha: 0.5}
|
||||
- {stop: 1, color: 0x8cc7ff, alpha: 1.0}
|
||||
indicator:
|
||||
bg:
|
||||
gradient:
|
||||
- {stop: 0, color: 0x565656}
|
||||
- {stop: 0.5, color: 0xbfbfbf}
|
||||
- {stop: 1, color: 0x565656}
|
||||
fg:
|
||||
active:
|
||||
gradient:
|
||||
- {stop: 0, color: 0x316BA6}
|
||||
- {stop: 0.5, color: 0x99CEFF}
|
||||
- {stop: 1, color: 0x316BA6}
|
||||
critical:
|
||||
gradient:
|
||||
- {stop: 0, color: 0xFF3333}
|
||||
- {stop: 0.5, color: 0xFFB8B8}
|
||||
- {stop: 1, color: 0xFF3333}
|
|
@ -0,0 +1,375 @@
|
|||
$schema: "http://json-schema.org/draft-07/schema#"
|
||||
description: over-engineered conky schema
|
||||
required: [modules, layout]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
|
||||
bootstrap:
|
||||
required: [update_interval, dimensions]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
update_interval:
|
||||
description: the update interval (seconds)
|
||||
type: number
|
||||
dimensions:
|
||||
description: the max width/height of the conky window
|
||||
type: array
|
||||
minItems: 2
|
||||
maxItems: 2
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
|
||||
# NOTE none of these are required
|
||||
modules:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
filesystem:
|
||||
required: [show_smart, fs_paths]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
show_smart:
|
||||
description: show the smart deamon indicator
|
||||
type: boolean
|
||||
fs_paths:
|
||||
description: the filesystem paths for which usage should be shown
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: object
|
||||
required: [name, path]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
|
||||
graphics:
|
||||
required: [show_temp, show_clock, show_gpu_util, show_mem_util, show_vid_util]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
dev_power:
|
||||
description: the sysfs path to the graphics card power indicator
|
||||
type: string
|
||||
show_temp:
|
||||
description: show the GPU temp
|
||||
type: boolean
|
||||
show_clock:
|
||||
description: show the GPU clock speeds
|
||||
type: boolean
|
||||
show_gpu_util:
|
||||
description: show the GPU utilization plot
|
||||
type: boolean
|
||||
show_mem_util:
|
||||
description: show the GPU memory utilization plot
|
||||
type: boolean
|
||||
show_vid_util:
|
||||
description: show the GPU video utilization plot
|
||||
type: boolean
|
||||
|
||||
memory:
|
||||
required: [show_stats, show_plot, table_rows]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
show_stats:
|
||||
description: show memory stats/dial
|
||||
type: boolean
|
||||
show_swap:
|
||||
description: show swap dial
|
||||
type: boolean
|
||||
show_plot:
|
||||
description: show the RAM utilization plot
|
||||
type: boolean
|
||||
table_rows: &table
|
||||
descrition: the number of rows in the table (0 for no table)
|
||||
type: integer
|
||||
minimum: 0
|
||||
|
||||
power:
|
||||
required: [battery, rapl_specs]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
battery:
|
||||
description: the battery device to use (or blank if none)
|
||||
type: string
|
||||
rapl_specs:
|
||||
description: the Intel RAPL specs for which plots should be made
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [name, address]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
address:
|
||||
type: string
|
||||
|
||||
processor:
|
||||
required: [core_rows, show_stats, show_plot, table_rows]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
core_rows:
|
||||
description: the number of rows over which to show discrete cores
|
||||
type: integer
|
||||
minimum: 0
|
||||
core_padding:
|
||||
description: horizontal padding to apply to the core layout
|
||||
type: integer
|
||||
minimum: 0
|
||||
show_stats:
|
||||
description: show frequency/HWP stats
|
||||
type: boolean
|
||||
show_plot:
|
||||
description: show CPU utilization plot
|
||||
type: boolean
|
||||
table_rows: *table
|
||||
|
||||
readwrite:
|
||||
required: [devices]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
devices:
|
||||
description: the devices to include in I/O summations
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
|
||||
layout:
|
||||
required: [anchor, panels]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
anchor:
|
||||
description: the coordinates of the upper-left corner to anchor the app
|
||||
type: array
|
||||
minItems: 2
|
||||
maxItems: 2
|
||||
items:
|
||||
type: integer
|
||||
panels:
|
||||
description: either a panel (object) or padding between panels (int)
|
||||
type: array
|
||||
items:
|
||||
anyOf:
|
||||
- type: integer
|
||||
minimum: 0
|
||||
- type: object
|
||||
description: layout for a single panel
|
||||
required: [columns, margins]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
margins:
|
||||
type: array
|
||||
minItems: 2
|
||||
maxItems: 2
|
||||
items:
|
||||
type: integer
|
||||
columns:
|
||||
description: |
|
||||
either the columns in this panel (object) or padding
|
||||
between columns (int)
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
anyOf:
|
||||
- type: integer
|
||||
minimum: 0
|
||||
- type: object
|
||||
required: [blocks, width]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
width:
|
||||
description: the width of all modules in this column
|
||||
type: integer
|
||||
minimum: 0
|
||||
blocks:
|
||||
description: |
|
||||
either a module name (string) or padding (int)
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
anyOf:
|
||||
- type: integer
|
||||
minimum: 0
|
||||
- type: string
|
||||
pattern:
|
||||
"^system|graphics|processor|readwrite|\
|
||||
network|pacman|filesystem|power|memory$"
|
||||
|
||||
theme:
|
||||
required: [font, geometry, patterns]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
font:
|
||||
required: [family, sizes]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
family:
|
||||
type: string
|
||||
sizes:
|
||||
required: [normal, plot_label, table, header]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
normal: &font_size
|
||||
type: integer
|
||||
minimum: 5
|
||||
plot_label: *font_size
|
||||
table: *font_size
|
||||
header: *font_size
|
||||
|
||||
geometry:
|
||||
required: [plot, table]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
plot:
|
||||
required: [seconds, ticks, height, spacing]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
spacing:
|
||||
description: the spacing between the label and the plot
|
||||
type: integer
|
||||
minimum: 10
|
||||
height:
|
||||
description: the height of the plot
|
||||
type: integer
|
||||
minimum: 10
|
||||
seconds:
|
||||
description: the number of seconds on each timeseries plot
|
||||
type: integer
|
||||
minimum: 30
|
||||
ticks:
|
||||
description: the number of ticks on the x/y axes
|
||||
type: array
|
||||
minItems: 2
|
||||
maxItems: 2
|
||||
items:
|
||||
type: integer
|
||||
minimum: 2
|
||||
table:
|
||||
required: [name_chars, padding, header_padding]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
name_chars:
|
||||
description: |
|
||||
the length to which the name column should be trimmed (if any)
|
||||
type: integer
|
||||
minimum: 0
|
||||
padding:
|
||||
description: the x/y padding around the table
|
||||
type: array
|
||||
minItems: 2
|
||||
maxItems: 2
|
||||
items:
|
||||
type: integer
|
||||
minimum: 0
|
||||
header_padding:
|
||||
description: the padding beneath the column headers
|
||||
type: integer
|
||||
minimum: 0
|
||||
row_spacing:
|
||||
description: the distance between the center of each row
|
||||
type: integer
|
||||
minimum: 10
|
||||
|
||||
header:
|
||||
required: [underline_offset, padding]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
underline_offset:
|
||||
description: the offset of the underline (from top of header)
|
||||
type: integer
|
||||
minimum: 10
|
||||
padding:
|
||||
description: the padding beneath the underline
|
||||
type: integer
|
||||
minimum: 0
|
||||
|
||||
patterns:
|
||||
required: [header, panel, text, border, plot, indicator]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
header: &pattern
|
||||
oneOf:
|
||||
- type: integer
|
||||
- type: object
|
||||
oneOf:
|
||||
- required: [color, alpha]
|
||||
- required: [gradient]
|
||||
- required: [gradient_alpha]
|
||||
properties:
|
||||
color:
|
||||
type: integer
|
||||
maximum: 0xffffff
|
||||
alpha: &alpha
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 1
|
||||
gradient:
|
||||
type: array
|
||||
minItems: 2
|
||||
items:
|
||||
type: object
|
||||
required: [stop, color]
|
||||
additionalProperties: false
|
||||
properties: &gradient
|
||||
stop:
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 1
|
||||
color:
|
||||
type: integer
|
||||
maximum: 0xffffff
|
||||
|
||||
gradient_alpha:
|
||||
type: array
|
||||
minItems: 2
|
||||
items:
|
||||
type: object
|
||||
required: [stop, color, alpha]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
<<: *gradient
|
||||
alpha: *alpha
|
||||
|
||||
panel:
|
||||
required: [bg]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
bg: *pattern
|
||||
|
||||
text:
|
||||
required: [active, inactive, critical]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
active: *pattern
|
||||
inactive: *pattern
|
||||
critical: *pattern
|
||||
|
||||
border: *pattern
|
||||
|
||||
plot:
|
||||
required: [grid, outline, data]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
grid: *pattern
|
||||
outline: *pattern
|
||||
data:
|
||||
required: [border, fill]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
border: *pattern
|
||||
fill: *pattern
|
||||
|
||||
indicator:
|
||||
required: [bg, fg]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
bg: *pattern
|
||||
fg:
|
||||
required: [active, critical]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
active: *pattern
|
||||
critical: *pattern
|
|
@ -54,7 +54,6 @@ package.cpath = conky_dir..'lib/lib/lua/5.4/?.so;'
|
|||
|
||||
local yaml = require 'lyaml'
|
||||
local i_o = require 'i_o'
|
||||
local validate = require 'validate'
|
||||
|
||||
local config_path = '/tmp/conky.yml'
|
||||
|
||||
|
@ -69,9 +68,7 @@ local find_valid_config = function(paths)
|
|||
local rc = try_read_config(path)
|
||||
if rc == 0 then
|
||||
i_o.infof('Using config at %s', path)
|
||||
local config = yaml.load(i_o.read_file(config_path))
|
||||
validate.validate_config(config)
|
||||
return config
|
||||
return yaml.load(i_o.read_file(config_path))
|
||||
else
|
||||
i_o.warnf('could not read %s; trying next', path)
|
||||
end
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
#! /bin/bash
|
||||
|
||||
# small script to log battery usage in txt file for conky to parse
|
||||
# lovingly stolen from battery-stats by petter reinholdtsen
|
||||
|
||||
DST_FILE="$XDG_CACHE_HOME/batmon.log"
|
||||
SYS_BAT_PATH="/sys/class/power_supply/BAT0"
|
||||
BAT_CAP_PATH="$SYS_BAT_PATH/charge_now"
|
||||
BAT_CAP_FULL_PATH="$SYS_BAT_PATH/charge_full"
|
||||
MAX_ENTRIES=1440 # number of minutes in day
|
||||
|
||||
read_bat_percent() {
|
||||
timestamp=$(date +%s)
|
||||
charge_now=$(cat "$BAT_CAP_PATH")
|
||||
charge_full=$(cat "$BAT_CAP_FULL_PATH")
|
||||
percent=$(echo "100 * $charge_now / $charge_full" | bc)
|
||||
echo $timestamp $percent >> "$DST_FILE"
|
||||
}
|
||||
|
||||
read_bat_percent
|
||||
# truncate to most recent entries by max_entries
|
||||
tail -n $MAX_ENTRIES "$DST_FILE" > /tmp/batmon.tmp.log && mv /tmp/batmon.tmp.log "$DST_FILE"
|
|
@ -1,63 +0,0 @@
|
|||
#! /bin/env python
|
||||
|
||||
# determine the status of the seafile client
|
||||
|
||||
import subprocess as sp
|
||||
import os
|
||||
|
||||
SEAF_FILE = "/tmp/.conky_seafile"
|
||||
|
||||
CMD = ["seaf-cli", "status"]
|
||||
|
||||
# possible statuses (according to the source here: https://github.com/haiwen/seafile/blob/91e5d897395c728a1e862dbdaf3d8a519c2ed73e/daemon/sync-mgr.c#L471)
|
||||
# ranked in order from least to most severe in terms of "being synced"
|
||||
STATUS_RANK = [
|
||||
"synchronized",
|
||||
"committing",
|
||||
"initializing",
|
||||
"downloading",
|
||||
"merging",
|
||||
"uploading",
|
||||
"error",
|
||||
"canceled",
|
||||
"cancel pending",
|
||||
]
|
||||
|
||||
|
||||
def get_conf_dir():
|
||||
try:
|
||||
conf_dir = os.environ["CCNET_CONF_DIR"]
|
||||
return ["-c", conf_dir]
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
|
||||
def collapse_statuses(xs):
|
||||
try:
|
||||
return STATUS_RANK[max(STATUS_RANK.index(x) for x in xs)]
|
||||
except ValueError:
|
||||
return "unknown"
|
||||
|
||||
|
||||
def query_statuses():
|
||||
args = get_conf_dir()
|
||||
res = sp.run(CMD + args, capture_output=True, check=True)
|
||||
raw = res.stdout.rstrip().split(b"\n")[1:]
|
||||
statuses = [ln.split(b"\t")[1].strip().decode() for ln in raw]
|
||||
return collapse_statuses(statuses)
|
||||
|
||||
|
||||
def write_output(status):
|
||||
with open(SEAF_FILE, "w", encoding="utf8") as f:
|
||||
f.write(status.capitalize())
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
status = query_statuses()
|
||||
write_output(status)
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
|
||||
|
||||
main()
|
|
@ -121,14 +121,8 @@ return function(config)
|
|||
)
|
||||
end
|
||||
|
||||
-- a conventional rounding function that behaves the way most humans were
|
||||
-- taught in preschool
|
||||
local _round = function(x)
|
||||
return math.floor(x + 0.5)
|
||||
end
|
||||
|
||||
local _format_percent_label = function(_)
|
||||
return function(z) return __string_format('%i%%', _round(z * 100)) end
|
||||
return function(z) return __string_format('%i%%', math.floor(z * 100)) end
|
||||
end
|
||||
|
||||
local _format_percent_maybe = function(z)
|
||||
|
@ -439,14 +433,6 @@ return function(config)
|
|||
)
|
||||
end
|
||||
|
||||
M.make_blank_dial = function(x, y, radius, thickness, threshold)
|
||||
return dial.make(
|
||||
geom.make_arc(x, y, radius, DIAL_THETA0, DIAL_THETA1),
|
||||
arc.config(style.line(thickness, CAP_BUTT), patterns.indicator.bg),
|
||||
threshold_indicator(threshold)
|
||||
)
|
||||
end
|
||||
|
||||
M.make_dial = function(x, y, radius, thickness, threshold, _format, pre_function)
|
||||
return {
|
||||
dial = dial.make(
|
||||
|
|
|
@ -29,28 +29,6 @@ return function(main_state, config, common, width, point)
|
|||
)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- seafile
|
||||
|
||||
local SEAF_FILE = '/tmp/.conky_seafile'
|
||||
|
||||
local mk_seafile = function(y)
|
||||
local obj = common.make_text_row(point.x, y, width, 'Seafile Daemon')
|
||||
local update = function()
|
||||
if main_state.trigger10 == 0 then
|
||||
local status = i_o.read_file(SEAF_FILE)
|
||||
common.text_row_set(obj, status or 'N/A')
|
||||
end
|
||||
end
|
||||
return common.mk_acc(
|
||||
width,
|
||||
0,
|
||||
update,
|
||||
pure.partial(common.text_row_draw_static, obj),
|
||||
pure.partial(common.text_row_draw_dynamic, obj)
|
||||
)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- filesystem bar chart
|
||||
|
||||
|
@ -101,10 +79,7 @@ return function(main_state, config, common, width, point)
|
|||
point = point,
|
||||
width = width,
|
||||
set_state = nil,
|
||||
top = {
|
||||
{mk_smart, config.show_smart, separator_bar_spacing},
|
||||
{mk_seafile, config.show_seafile, separator_bar_spacing}
|
||||
},
|
||||
top = {{mk_smart, config.show_smart, separator_bar_spacing}},
|
||||
common.mk_section(separator_bar_spacing, {mk_bars, true, 0})
|
||||
}
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ local i_o = require 'i_o'
|
|||
|
||||
return function(update_freq, config, common, width, point)
|
||||
local NA = 'N/A'
|
||||
local NVIDIA_EXE = 'nvidia-smi'
|
||||
local NVIDIA_EXE = 'nvidia-settings'
|
||||
|
||||
local geo = config.geometry
|
||||
local sep_spacing = geo.sep_spacing
|
||||
|
@ -20,97 +20,55 @@ return function(update_freq, config, common, width, point)
|
|||
|
||||
i_o.assert_exe_exists(NVIDIA_EXE)
|
||||
|
||||
local mk_query_cmd = function(props)
|
||||
return __string_format(
|
||||
'%s --query-gpu=%s --format=csv,noheader,nounits',
|
||||
NVIDIA_EXE,
|
||||
pure.collapse(props, ',')
|
||||
)
|
||||
end
|
||||
-- vars to process the nv settings glob
|
||||
--
|
||||
-- glob will be of the form:
|
||||
-- <used_mem>
|
||||
-- <total_mem>
|
||||
-- <temp>
|
||||
-- <gpu_freq>,<mem_freq>
|
||||
-- graphics=<gpu_util>, memory=<mem_util>, video=<vid_util>, PCIe=<pci_util>
|
||||
local NV_QUERY = NVIDIA_EXE..
|
||||
' -t'..
|
||||
' -q UsedDedicatedGPUmemory'..
|
||||
' -q TotalDedicatedGPUmemory'..
|
||||
' -q ThermalSensorReading'..
|
||||
' -q [gpu:0]/GPUCurrentClockFreqs'..
|
||||
' -q [gpu:0]/GPUutilization'..
|
||||
' 2>/dev/null'
|
||||
|
||||
-- TODO also use encoder for video util?
|
||||
-- TODO add video clock speed?
|
||||
local query_stats = mk_query_cmd(
|
||||
{
|
||||
'memory.used',
|
||||
'temperature.gpu',
|
||||
'clocks.gr',
|
||||
'clocks.mem',
|
||||
'utilization.gpu',
|
||||
'utilization.decoder'
|
||||
}
|
||||
)
|
||||
|
||||
local NV_REGEX = '(%d+), (%d+), (%d+), (%d+), (%d+), (%d+)'
|
||||
local NV_REGEX = '(%d+)\n'..
|
||||
'(%d+)\n'..
|
||||
'(%d+)\n'..
|
||||
'(%d+),(%d+)\n'..
|
||||
'graphics=(%d+), memory=%d+, video=(%d+), PCIe=%d+\n'
|
||||
|
||||
local mod_state = {
|
||||
error = false,
|
||||
total_memory = 0,
|
||||
gpu_frequency = 0,
|
||||
memory_frequency = 0,
|
||||
used_memory = 0,
|
||||
total_memory = 0,
|
||||
temp_reading = 0,
|
||||
gpu_utilization = 0,
|
||||
vid_utilization = 0
|
||||
}
|
||||
|
||||
local sleep_token = 0
|
||||
local sleep_limit = config.power.cycle_seconds
|
||||
local gpu_idle_freq_limit = config.power.freq_limit
|
||||
|
||||
-- TODO ensure this file exists
|
||||
local runtime_status_file = config.dev_power..'/runtime_status'
|
||||
|
||||
local want_nvidia_query = config.show_temp or config.show_clock
|
||||
or config.gpu_util or config.mem_util or config.vid_util
|
||||
|
||||
local update_state = function()
|
||||
local is_active = i_o.read_file(runtime_status_file, nil, '*l') == 'active'
|
||||
-- this will make the nvidia-smi query fire only so often when the clock
|
||||
-- is below a certain threshold. This is necessary to get the GPU to
|
||||
-- suspend when nothing is 'using' it, at the cost of lowering the
|
||||
-- response time for when it eventually is used again. Maybe won't
|
||||
-- matter that much since the jobs that use the GPU tend to be long
|
||||
-- anyways, so a few seconds won't hurt. Furthermore, there are ways to
|
||||
-- wake this up manually by detecting certain processes the likely will
|
||||
-- use the GPU (ffmpeg and friends) or detecting processes that are
|
||||
-- holding /dev/nvidia* files (which isn't foolproof but it will capture
|
||||
-- most events)
|
||||
if is_active and
|
||||
mod_state.gpu_frequency > 0 and
|
||||
mod_state.gpu_frequency < gpu_idle_freq_limit then
|
||||
if sleep_token < sleep_limit - 1 then
|
||||
sleep_token = sleep_token + 1
|
||||
else
|
||||
sleep_token = 0
|
||||
end
|
||||
else
|
||||
sleep_token = 0
|
||||
end
|
||||
if is_active and want_nvidia_query and sleep_token == 0 then
|
||||
local nvidia_settings_glob = i_o.execute_cmd(query_stats)
|
||||
if i_o.read_file(config.dev_power, nil, '*l') == 'on' then
|
||||
local nvidia_settings_glob = i_o.execute_cmd(NV_QUERY)
|
||||
if nvidia_settings_glob == nil then
|
||||
mod_state.error = 'Error'
|
||||
else
|
||||
mod_state.used_memory,
|
||||
mod_state.total_memory,
|
||||
mod_state.temp_reading,
|
||||
mod_state.gpu_frequency,
|
||||
mod_state.memory_frequency,
|
||||
mod_state.gpu_utilization,
|
||||
mod_state.vid_utilization
|
||||
= __string_match(nvidia_settings_glob, NV_REGEX)
|
||||
mod_state.gpu_frequency = __tonumber(mod_state.gpu_frequency)
|
||||
mod_state.error = false
|
||||
end
|
||||
-- query total memory if we need it (do this here so we don't turn on
|
||||
-- the gpu every time we start conky)
|
||||
if mod_state.total_memory == 0 and config.show_mem_util then
|
||||
mod_state.total_memory = __tonumber(i_o.execute_cmd(mk_query_cmd({'memory.total'})))
|
||||
end
|
||||
elseif is_active then
|
||||
mod_state.error = false
|
||||
else
|
||||
mod_state.gpu_frequency = 0
|
||||
mod_state.error = 'Off'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,8 +26,6 @@ return function(update_freq, config, common, width, point)
|
|||
|
||||
local mk_rate_plot = function(label, address, y)
|
||||
local read_joules = sys.intel_powercap_reader(address)
|
||||
local read_joules0 = function() return read_joules() or 0 end
|
||||
local read_joulesNA = function() return read_joules() or 'N/A' end
|
||||
local obj = common.make_rate_timeseries(
|
||||
point.x,
|
||||
y,
|
||||
|
@ -40,12 +38,12 @@ return function(update_freq, config, common, width, point)
|
|||
label,
|
||||
0,
|
||||
update_freq,
|
||||
read_joules0()
|
||||
read_joules()
|
||||
)
|
||||
return common.mk_acc(
|
||||
width,
|
||||
plot_height + plot_sec_break,
|
||||
function(_) common.update_rate_timeseries(obj, read_joulesNA()) end,
|
||||
function(_) common.update_rate_timeseries(obj, read_joules()) end,
|
||||
mk_static(obj),
|
||||
mk_dynamic(obj)
|
||||
)
|
||||
|
@ -67,11 +65,8 @@ return function(update_freq, config, common, width, point)
|
|||
local mk_bat = function(y)
|
||||
local _read_battery_power = sys.battery_power_reader(config.battery)
|
||||
|
||||
-- TODO this is actually telling the plot to say it is on AC when the
|
||||
-- battery status can't be read (in which case it should say ERROR or
|
||||
-- something)
|
||||
local read_battery_power = function(is_using_ac)
|
||||
return is_using_ac and 0 or (_read_battery_power() or 0)
|
||||
return is_using_ac and 0 or _read_battery_power()
|
||||
end
|
||||
local read_bat_status = sys.battery_status_reader(config.battery)
|
||||
local obj = common.make_tagged_scaled_timeseries(
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
local dial = require 'dial'
|
||||
local compound_dial = require 'compound_dial'
|
||||
local text_table = require 'text_table'
|
||||
local i_o = require 'i_o'
|
||||
|
@ -6,7 +5,6 @@ local cpu = require 'sys'
|
|||
local pure = require 'pure'
|
||||
|
||||
local __math_floor = math.floor
|
||||
local __string_format = string.format
|
||||
|
||||
return function(update_freq, main_state, config, common, width, point)
|
||||
local dial_inner_radius = 30
|
||||
|
@ -23,51 +21,44 @@ return function(update_freq, main_state, config, common, width, point)
|
|||
-----------------------------------------------------------------------------
|
||||
-- processor state
|
||||
|
||||
local topology = cpu.get_core_topology()
|
||||
local mod_state = cpu.read_cpu_loads(cpu.init_cpu_loads(topology))
|
||||
local ncpus = cpu.get_cpu_number(topology)
|
||||
local mod_state = cpu.read_cpu_loads(cpu.init_cpu_loads())
|
||||
|
||||
local update_state = function()
|
||||
mod_state = cpu.read_cpu_loads(mod_state)
|
||||
end
|
||||
|
||||
-- test that the core groups we want are actually displayable
|
||||
for _, g in pairs(config.core_groups) do
|
||||
i_o.assertf(
|
||||
topology[g.threads] ~= nil,
|
||||
'processor: no group with %s threads', 1
|
||||
)
|
||||
local ncores = #topology[g.threads]
|
||||
i_o.assertf(
|
||||
math.fmod(ncores, g.rows) == 0,
|
||||
'processor: could not evenly divide %s cores into %s rows', ncores, g.rows
|
||||
)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- cores (loads and temps)
|
||||
|
||||
local create_core = function(core_cols, y, nthreads, padding, c)
|
||||
local ncpus = cpu.get_cpu_number()
|
||||
local ncores = cpu.get_core_number()
|
||||
local nthreads = ncpus / ncores
|
||||
|
||||
local show_cores = false
|
||||
|
||||
if config.core_rows > 0 then
|
||||
if math.fmod(ncores, config.core_rows) == 0 then
|
||||
show_cores = true
|
||||
else
|
||||
i_o.warnf(
|
||||
'could not evenly distribute %i cores over %i rows; disabling',
|
||||
ncores,
|
||||
config.core_rows
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local create_core = function(core_cols, y, c)
|
||||
local dial_x = point.x +
|
||||
(core_cols == 1
|
||||
and (width / 2)
|
||||
or (padding + dial_outer_radius +
|
||||
(width - 2 * (dial_outer_radius + padding))
|
||||
or (config.core_padding + dial_outer_radius +
|
||||
(width - 2 * (dial_outer_radius + config.core_padding))
|
||||
* math.fmod(c - 1, core_cols) / (core_cols - 1)))
|
||||
local dial_y = y + dial_outer_radius +
|
||||
(2 * dial_outer_radius + dial_y_spacing)
|
||||
* math.floor((c - 1) / core_cols)
|
||||
local loads
|
||||
if nthreads == 1 then
|
||||
local single_thickness = dial_outer_radius - dial_inner_radius
|
||||
loads = common.make_blank_dial(
|
||||
dial_x,
|
||||
dial_y,
|
||||
dial_outer_radius - single_thickness / 2,
|
||||
single_thickness,
|
||||
80
|
||||
)
|
||||
else
|
||||
return {
|
||||
loads = common.make_compound_dial(
|
||||
dial_x,
|
||||
dial_y,
|
||||
|
@ -76,10 +67,7 @@ return function(update_freq, main_state, config, common, width, point)
|
|||
dial_thickness,
|
||||
80,
|
||||
nthreads
|
||||
)
|
||||
end
|
||||
return {
|
||||
loads = loads,
|
||||
),
|
||||
coretemp = common.make_text_circle(
|
||||
dial_x,
|
||||
dial_y,
|
||||
|
@ -91,62 +79,44 @@ return function(update_freq, main_state, config, common, width, point)
|
|||
}
|
||||
end
|
||||
|
||||
local read_load = function(core_topology, phy_core_id, thread_id)
|
||||
local i = core_topology[phy_core_id].cpus[thread_id].lgl_cpu_id
|
||||
return mod_state[i].percent_active * 100
|
||||
local mk_cores = function(y)
|
||||
local core_cols = ncores / config.core_rows
|
||||
local cores = pure.map_n(pure.partial(create_core, core_cols, y), ncores)
|
||||
local coretemp_paths = cpu.get_coretemp_paths()
|
||||
if #coretemp_paths ~= ncores then
|
||||
i_o.warnf('could not find all coretemp paths')
|
||||
end
|
||||
|
||||
local get_load_functions = function(cores, nthreads, core_topology)
|
||||
if nthreads == 1 then
|
||||
local update = function(c)
|
||||
dial.set(cores[c].loads, read_load(core_topology, c, 1))
|
||||
end
|
||||
return update, dial.draw_static, dial.draw_dynamic
|
||||
else
|
||||
local update = function(c)
|
||||
for t = 1, nthreads do
|
||||
compound_dial.set(cores[c].loads, t, read_load(core_topology, c, t))
|
||||
local update_coretemps = function()
|
||||
for conky_core_idx, path in pairs(coretemp_paths) do
|
||||
local temp = __math_floor(0.001 * i_o.read_file(path, nil, '*n'))
|
||||
common.text_circle_set(cores[conky_core_idx].coretemp, temp)
|
||||
end
|
||||
end
|
||||
return update, compound_dial.draw_static, compound_dial.draw_dynamic
|
||||
end
|
||||
end
|
||||
|
||||
local mk_core_group = function(group_config, y)
|
||||
local nthreads = group_config.threads
|
||||
local core_topology = topology[nthreads]
|
||||
local ncores = #core_topology
|
||||
local core_cols = ncores / group_config.rows
|
||||
local _create_core = pure.partial(
|
||||
create_core, core_cols, y, nthreads, group_config.padding
|
||||
)
|
||||
local cores = pure.map_n(_create_core, ncores)
|
||||
local update_loads, draw_static_loads, draw_dynamic_loads =
|
||||
get_load_functions(cores, nthreads, core_topology)
|
||||
local update = function()
|
||||
for c = 1, ncores do
|
||||
local temp = __math_floor(
|
||||
0.001 * i_o.read_file(core_topology[c].coretemp_path, nil, '*n')
|
||||
for _, load_data in pairs(mod_state) do
|
||||
compound_dial.set(
|
||||
cores[load_data.conky_core_idx].loads,
|
||||
load_data.conky_thread_id,
|
||||
load_data.percent_active * 100
|
||||
)
|
||||
common.text_circle_set(cores[c].coretemp, temp)
|
||||
update_loads(c)
|
||||
end
|
||||
update_coretemps()
|
||||
end
|
||||
local static = function(cr)
|
||||
for i = 1, ncores do
|
||||
for i = 1, #cores do
|
||||
common.text_circle_draw_static(cores[i].coretemp, cr)
|
||||
draw_static_loads(cores[i].loads, cr)
|
||||
compound_dial.draw_static(cores[i].loads, cr)
|
||||
end
|
||||
end
|
||||
local dynamic = function(cr)
|
||||
for i = 1, ncores do
|
||||
for i = 1, #cores do
|
||||
common.text_circle_draw_dynamic(cores[i].coretemp, cr)
|
||||
draw_dynamic_loads(cores[i].loads, cr)
|
||||
compound_dial.draw_dynamic(cores[i].loads, cr)
|
||||
end
|
||||
end
|
||||
return common.mk_acc(
|
||||
width,
|
||||
(dial_outer_radius * 2 + dial_y_spacing) * group_config.rows
|
||||
(dial_outer_radius * 2 + dial_y_spacing) * config.core_rows
|
||||
- dial_y_spacing,
|
||||
update,
|
||||
static,
|
||||
|
@ -158,24 +128,13 @@ return function(update_freq, main_state, config, common, width, point)
|
|||
-- HWP status
|
||||
|
||||
local mk_hwp_freq = function(y)
|
||||
local hwp_paths = cpu.get_hwp_paths(topology)
|
||||
local freq_labels
|
||||
local cpu_group_map = cpu.topology_to_cpu_map(topology)
|
||||
local ngroups = pure.length(topology)
|
||||
local format_label = function(group_id)
|
||||
return __string_format('Ave Freq (%i)', group_id)
|
||||
end
|
||||
if ngroups == 1 then
|
||||
freq_labels = {'Ave Freq'}
|
||||
else
|
||||
freq_labels = pure.map_n(format_label, ngroups)
|
||||
end
|
||||
local hwp_paths = cpu.get_hwp_paths()
|
||||
local cpu_status = common.make_text_rows(
|
||||
point.x,
|
||||
y,
|
||||
width,
|
||||
text_spacing,
|
||||
pure.concat({'HWP Preference'}, freq_labels)
|
||||
{'HWP Preference', 'Ave Freq'}
|
||||
)
|
||||
local update = function()
|
||||
-- For some reason this call is slow (querying anything with pstate in
|
||||
|
@ -184,18 +143,13 @@ return function(update_freq, main_state, config, common, width, point)
|
|||
if main_state.trigger10 == 0 then
|
||||
common.text_rows_set(cpu_status, 1, cpu.read_hwp(hwp_paths))
|
||||
end
|
||||
local ave_freqs = cpu.read_ave_freqs(topology, cpu_group_map)
|
||||
local i = 2
|
||||
for group_id, _ in pairs(topology) do
|
||||
common.text_rows_set(cpu_status, i, ave_freqs[group_id])
|
||||
i = i + 1
|
||||
end
|
||||
common.text_rows_set(cpu_status, 2, cpu.read_freq())
|
||||
end
|
||||
local static = pure.partial(common.text_rows_draw_static, cpu_status)
|
||||
local dynamic = pure.partial(common.text_rows_draw_dynamic, cpu_status)
|
||||
return common.mk_acc(
|
||||
width,
|
||||
text_spacing * ngroups,
|
||||
text_spacing,
|
||||
update,
|
||||
static,
|
||||
dynamic
|
||||
|
@ -218,7 +172,7 @@ return function(update_freq, main_state, config, common, width, point)
|
|||
)
|
||||
local update = function()
|
||||
local s = 0
|
||||
for i = 1, ncpus do
|
||||
for i = 1, #mod_state do
|
||||
s = s + mod_state[i].percent_active
|
||||
end
|
||||
common.tagged_percent_timeseries_set(total_load, s / ncpus * 100)
|
||||
|
@ -271,19 +225,15 @@ return function(update_freq, main_state, config, common, width, point)
|
|||
-----------------------------------------------------------------------------
|
||||
-- main functions
|
||||
|
||||
local core_group_section = function (g)
|
||||
return {pure.partial(mk_core_group, g), true, text_spacing}
|
||||
end
|
||||
|
||||
return {
|
||||
header = 'PROCESSOR',
|
||||
point = point,
|
||||
width = width,
|
||||
set_state = update_state,
|
||||
top = pure.concat(
|
||||
pure.map(core_group_section, config.core_groups),
|
||||
{{mk_hwp_freq, config.show_stats, sep_spacing}}
|
||||
),
|
||||
top = {
|
||||
{mk_cores, show_cores, text_spacing},
|
||||
{mk_hwp_freq, config.show_stats, sep_spacing},
|
||||
},
|
||||
common.mk_section(
|
||||
sep_spacing,
|
||||
{mk_load_plot, config.show_plot, geo.table.sec_break},
|
||||
|
|
|
@ -2,52 +2,17 @@ local i_o = require 'i_o'
|
|||
local pure = require 'pure'
|
||||
|
||||
return function(main_state, config, common, width, point)
|
||||
local IP_EXE = 'ip'
|
||||
local TEST_IP = '104.18.114.97' -- ipv4.icanhazip.com
|
||||
local IP_REGEX = 'via ([^ ]+)'
|
||||
local DEV_REGEX = 'dev ([^ ]+)'
|
||||
local ERR = 'Error'
|
||||
|
||||
local text_spacing = config.geometry.text_spacing
|
||||
|
||||
local __string_match = string.match
|
||||
|
||||
i_o.assert_exe_exists(IP_EXE)
|
||||
|
||||
-- NOTE conky has a builtin gateway function but it will only output
|
||||
-- whatever the output of 'ip route' is, which won't pick up changes that
|
||||
-- some VPNs (like tailscale) will make to the rest of the routing table.
|
||||
-- More reliable way to test the "default route" is to ask the routing table
|
||||
-- how it would route to a public IP, which is what we do here.
|
||||
local ip_cmd = IP_EXE..' route get '..TEST_IP
|
||||
|
||||
local get_gateway = function()
|
||||
local ip_glob = i_o.execute_cmd(ip_cmd)
|
||||
if ip_glob == nil then
|
||||
return ERR
|
||||
else
|
||||
local ip = __string_match(ip_glob, IP_REGEX)
|
||||
if ip ~= nil then
|
||||
return ip
|
||||
end
|
||||
|
||||
local dev = __string_match(ip_glob, DEV_REGEX)
|
||||
if dev ~= nil then
|
||||
return dev
|
||||
end
|
||||
|
||||
return ERR
|
||||
end
|
||||
end
|
||||
|
||||
local mk_stats = function(y)
|
||||
local obj = common.make_text_rows(
|
||||
point.x,
|
||||
y,
|
||||
width,
|
||||
text_spacing,
|
||||
-- {'Kernel', 'Uptime', 'Last Upgrade', 'Last Sync', 'Gateway'}
|
||||
{'Kernel', 'Uptime', 'Last Upgrade', 'Gateway'}
|
||||
{'Kernel', 'Uptime', 'Last Upgrade', 'Last Sync'}
|
||||
)
|
||||
-- just update this once
|
||||
common.text_rows_set(obj, 1, i_o.conky('$kernel'))
|
||||
|
@ -60,12 +25,11 @@ return function(main_state, config, common, width, point)
|
|||
"^%d+%s+([^%s]+)%s+([^%s]+).*"
|
||||
)
|
||||
common.text_rows_set(obj, 3, last_update)
|
||||
-- common.text_rows_set(obj, 4, last_sync)
|
||||
common.text_rows_set(obj, 4, last_sync)
|
||||
else
|
||||
common.text_rows_set(obj, 3, 'N/A')
|
||||
-- common.text_rows_set(obj, 4, 'N/A')
|
||||
common.text_rows_set(obj, 4, 'N/A')
|
||||
end
|
||||
common.text_rows_set(obj, 4, get_gateway())
|
||||
end
|
||||
local static = pure.partial(common.text_rows_draw_static, obj)
|
||||
local dynamic = pure.partial(common.text_rows_draw_dynamic, obj)
|
||||
|
|
86
src/pure.lua
86
src/pure.lua
|
@ -4,7 +4,6 @@ local err = require 'err'
|
|||
|
||||
local __math_floor = math.floor
|
||||
local __table_insert = table.insert
|
||||
local __string_format = string.format
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- zippy functions
|
||||
|
@ -51,52 +50,9 @@ M.reduce = function(f, init, seq)
|
|||
end
|
||||
end
|
||||
|
||||
M.member = function(x, seq)
|
||||
return M.reduce(
|
||||
function(acc, next) return acc or next == x end,
|
||||
false,
|
||||
M.values(seq)
|
||||
)
|
||||
end
|
||||
|
||||
M.collapse = function(seq, delim)
|
||||
if #seq == 0 then
|
||||
return ''
|
||||
elseif #seq == 1 then
|
||||
return seq[1]
|
||||
else
|
||||
local init, rest = M.head(seq)
|
||||
return M.reduce(
|
||||
function(acc, next) return __string_format('%s%s%s', acc, delim, next) end,
|
||||
init,
|
||||
rest
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- mappy functions
|
||||
|
||||
M.keys = function(seq)
|
||||
local r = {}
|
||||
local n = 1
|
||||
for k, _ in pairs(seq) do
|
||||
r[n] = k
|
||||
n = n + 1
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
M.values = function(seq)
|
||||
local r = {}
|
||||
local n = 1
|
||||
for _, v in pairs(seq) do
|
||||
r[n] = v
|
||||
n = n + 1
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
M.map = function(f, seq, ...)
|
||||
local r = {}
|
||||
for i = 1, #seq do
|
||||
|
@ -180,22 +136,6 @@ end
|
|||
--------------------------------------------------------------------------------
|
||||
-- random list things
|
||||
|
||||
M.length = function(tbl)
|
||||
local n = 0
|
||||
for _, _ in pairs(tbl) do
|
||||
n = n + 1
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
M.head = function(seq)
|
||||
local r = {}
|
||||
for i = 2, #seq do
|
||||
r[i - 1] = seq[i]
|
||||
end
|
||||
return seq[1], r
|
||||
end
|
||||
|
||||
-- a stupid but composable function
|
||||
M.get = function(key, tbl)
|
||||
return tbl[key]
|
||||
|
@ -249,32 +189,6 @@ M.flatten = function(xs)
|
|||
return r
|
||||
end
|
||||
|
||||
M.group_with = function(keyfun, valfun, seq)
|
||||
local f = function(acc, next)
|
||||
local k = keyfun(next)
|
||||
local v = valfun(next)
|
||||
if acc[k] == nil then
|
||||
acc[k] = {v}
|
||||
else
|
||||
acc[k][#acc[k] + 1] = v
|
||||
end
|
||||
return acc
|
||||
end
|
||||
return M.reduce(f, {}, seq)
|
||||
end
|
||||
|
||||
M.group_by = function(k, seq)
|
||||
local f = function(acc, next)
|
||||
if acc[k] == nil then
|
||||
acc[k] = {next}
|
||||
else
|
||||
acc[k][#acc[k]] = next
|
||||
end
|
||||
return acc
|
||||
end
|
||||
return M.reduce(f, {}, seq)
|
||||
end
|
||||
|
||||
M.concat = function(...)
|
||||
return M.flatten({...})
|
||||
end
|
||||
|
|
222
src/sys.lua
222
src/sys.lua
|
@ -13,12 +13,7 @@ local dirname = function(s)
|
|||
end
|
||||
|
||||
local read_micro = function(path)
|
||||
local j = i_o.read_file(path, nil, '*n')
|
||||
if j == nil then
|
||||
return nil
|
||||
else
|
||||
return j * 0.000001
|
||||
end
|
||||
return i_o.read_file(path, nil, '*n') * 0.000001
|
||||
end
|
||||
|
||||
local gmatch_to_table1 = function(pat, s)
|
||||
|
@ -93,10 +88,10 @@ end
|
|||
--------------------------------------------------------------------------------
|
||||
-- intel powercap
|
||||
|
||||
M.SYSFS_RAPL = '/sys/class/powercap'
|
||||
local SYSFS_RAPL = '/sys/class/powercap'
|
||||
|
||||
M.intel_powercap_reader = function(dev)
|
||||
local uj = __string_format('%s/%s/energy_uj', M.SYSFS_RAPL, dev)
|
||||
local uj = __string_format('%s/%s/energy_uj', SYSFS_RAPL, dev)
|
||||
i_o.assert_file_readable(uj)
|
||||
return function() return read_micro(uj) end
|
||||
end
|
||||
|
@ -104,10 +99,10 @@ end
|
|||
--------------------------------------------------------------------------------
|
||||
-- battery
|
||||
|
||||
M.SYSFS_POWER = '/sys/class/power_supply'
|
||||
local SYSFS_POWER = '/sys/class/power_supply'
|
||||
|
||||
local format_power_path = function(battery, property)
|
||||
local p = __string_format('%s/%s/%s', M.SYSFS_POWER, battery, property)
|
||||
local p = __string_format('%s/%s/%s', SYSFS_POWER, battery, property)
|
||||
i_o.assert_file_readable(p)
|
||||
return p
|
||||
end
|
||||
|
@ -115,15 +110,7 @@ end
|
|||
M.battery_power_reader = function(battery)
|
||||
local current = format_power_path(battery, 'current_now')
|
||||
local voltage = format_power_path(battery, 'voltage_now')
|
||||
return function()
|
||||
local c = read_micro(current)
|
||||
local v = read_micro(voltage)
|
||||
if c == nil or v == nil then
|
||||
return nil
|
||||
else
|
||||
return c * v
|
||||
end
|
||||
end
|
||||
return function() return read_micro(current) * read_micro(voltage) end
|
||||
end
|
||||
|
||||
M.battery_status_reader = function(battery)
|
||||
|
@ -185,12 +172,14 @@ end
|
|||
--------------------------------------------------------------------------------
|
||||
-- cpu
|
||||
|
||||
M.get_cpu_number = function(topology)
|
||||
local n = 0
|
||||
for g, c in pairs(topology) do
|
||||
n = n + g * #c
|
||||
end
|
||||
return n
|
||||
-- ASSUME nproc and lscpu will always be available
|
||||
|
||||
M.get_core_number = function()
|
||||
return __tonumber(i_o.read_file('/proc/cpuinfo', 'cpu cores%s+:%s(%d+)'))
|
||||
end
|
||||
|
||||
M.get_cpu_number = function()
|
||||
return __tonumber(i_o.execute_cmd('nproc', nil, '*n'))
|
||||
end
|
||||
|
||||
local get_coretemp_dir = function()
|
||||
|
@ -199,134 +188,89 @@ local get_coretemp_dir = function()
|
|||
return pure.fmap_maybe(dirname, s)
|
||||
end
|
||||
|
||||
-- return a table with keys corresponding to physcial core id and values to
|
||||
-- the number of threads of each core (usually 1 or 2)
|
||||
M.get_core_threads = function()
|
||||
i_o.assert_exe_exists('lscpu')
|
||||
local cmd = 'lscpu -y -p=core | grep -v \'^#\' | sort -k1,1n | uniq -c'
|
||||
local flip = function(c) return {__tonumber(c[2]), __tonumber(c[1])} end
|
||||
-- map cores to integer values starting at 1; this is necessary since some cpus
|
||||
-- don't report their core id's as a sequence of integers starting at 0
|
||||
local get_core_id_indexer = function()
|
||||
local make_indexer = pure.compose(
|
||||
pure.array_to_map,
|
||||
pure.partial(pure.map, flip),
|
||||
pure.partial(gmatch_to_tableN, '(%d+) (%d+)')
|
||||
pure.partial(pure.imap, function(i, c) return {__tonumber(c), i} end),
|
||||
pure.partial(gmatch_to_table1, '(%d+)')
|
||||
)
|
||||
return pure.fmap_maybe(
|
||||
make_indexer,
|
||||
i_o.execute_cmd('lscpu -p=CORE | tail -n+5 | sort | uniq')
|
||||
)
|
||||
return pure.fmap_maybe(make_indexer, i_o.execute_cmd(cmd))
|
||||
end
|
||||
|
||||
local get_coretemp_mapper = function()
|
||||
i_o.assert_exe_exists('grep')
|
||||
local get_core_mappings = function()
|
||||
local ncores = M.get_core_number()
|
||||
local map_ids = function(indexer)
|
||||
local f = function(acc, next)
|
||||
local cpu_id = __tonumber(next[1]) + 1
|
||||
local core_id = next[2]
|
||||
local conky_core_idx = indexer[__tonumber(core_id)]
|
||||
acc.mappings[cpu_id] = {
|
||||
conky_core_idx = conky_core_idx,
|
||||
conky_thread_id = acc.thread_ids[conky_core_idx],
|
||||
}
|
||||
acc.thread_ids[conky_core_idx] = acc.thread_ids[conky_core_idx] + 1
|
||||
return acc
|
||||
end
|
||||
local cpu_to_core_map = pure.maybe(
|
||||
{},
|
||||
pure.partial(gmatch_to_tableN, '(%d+),(%d+)'),
|
||||
i_o.execute_cmd('lscpu -p=cpu,CORE | tail -n+5')
|
||||
)
|
||||
local init = {mappings = {}, thread_ids = pure.rep(ncores, 1)}
|
||||
return pure.reduce(f, init, cpu_to_core_map).mappings
|
||||
end
|
||||
return pure.fmap_maybe(map_ids, get_core_id_indexer())
|
||||
end
|
||||
|
||||
M.get_coretemp_paths = function()
|
||||
local get_paths = function(indexer)
|
||||
local d = get_coretemp_dir()
|
||||
i_o.assert_exe_exists('grep')
|
||||
local get_labels = pure.compose(
|
||||
i_o.execute_cmd,
|
||||
pure.partial(__string_format, 'grep Core %s/temp*_label', true)
|
||||
)
|
||||
local to_tuple = function(m)
|
||||
return {__tonumber(m[2]), __string_format('%s/%s_input', d, m[1])}
|
||||
return {
|
||||
indexer[__tonumber(m[2])],
|
||||
__string_format('%s/%s_input', d, m[1])
|
||||
}
|
||||
end
|
||||
local to_map = pure.compose(
|
||||
local f = pure.compose(
|
||||
pure.array_to_map,
|
||||
pure.partial(pure.map, to_tuple),
|
||||
pure.partial(gmatch_to_tableN, '/([^/\n]+)_label:Core (%d+)\n')
|
||||
)
|
||||
return pure.maybe({}, to_map, pure.fmap_maybe(get_labels, d))
|
||||
return pure.maybe({}, f, pure.fmap_maybe(get_labels, d))
|
||||
end
|
||||
return pure.maybe({}, get_paths, get_core_id_indexer())
|
||||
end
|
||||
|
||||
M.get_core_topology = function()
|
||||
i_o.assert_exe_exists('lscpu')
|
||||
i_o.assert_exe_exists('grep')
|
||||
i_o.assert_exe_exists('sort')
|
||||
local coretemp_paths = get_coretemp_mapper()
|
||||
local assign_cpu = function(i, x)
|
||||
return {
|
||||
lgl_cpu_id = i,
|
||||
phy_core_id = __tonumber(x[1]),
|
||||
phy_cpu_id = __tonumber(x[2])
|
||||
}
|
||||
local match_freq = function(c)
|
||||
local f = 0
|
||||
local n = 0
|
||||
for s in __string_gmatch(c, '(%d+%.%d+)') do
|
||||
f = f + __tonumber(s)
|
||||
n = n + 1
|
||||
end
|
||||
local assign_core = function(acc, next)
|
||||
local g = acc.grouped
|
||||
local max_lgl_core_id = #g
|
||||
local new_phy_core_id = next.phy_core_id
|
||||
local new_cpu = {phy_cpu_id = next.phy_cpu_id, lgl_cpu_id = next.lgl_cpu_id}
|
||||
if acc.prev_phy_core_id == new_phy_core_id then
|
||||
local max_thread = #acc.grouped[max_lgl_core_id].cpus
|
||||
acc.grouped[max_lgl_core_id].cpus[max_thread + 1] = new_cpu
|
||||
else
|
||||
local new_lgl_core_id = max_lgl_core_id + 1
|
||||
acc.grouped[new_lgl_core_id] = {
|
||||
phy_core_id = new_phy_core_id,
|
||||
lgl_core_id = new_lgl_core_id,
|
||||
coretemp_path = coretemp_paths[new_phy_core_id],
|
||||
cpus = {new_cpu}
|
||||
}
|
||||
acc.prev_phy_core_id = new_phy_core_id
|
||||
end
|
||||
return acc
|
||||
end
|
||||
local get_threads = function(x)
|
||||
return #x.cpus
|
||||
end
|
||||
local f = pure.compose(
|
||||
pure.partial(pure.group_with, get_threads, pure.id),
|
||||
pure.partial(pure.get, 'grouped'),
|
||||
pure.partial(pure.reduce, assign_core, {prev_phy_core_id = -1, grouped = {}}),
|
||||
pure.partial(pure.imap, assign_cpu),
|
||||
pure.partial(gmatch_to_tableN, '(%d+),(%d+)')
|
||||
)
|
||||
local out =
|
||||
i_o.execute_cmd('lscpu -y -p=core,cpu | grep -v \'^#\' | sort -k1,1n -t,')
|
||||
return pure.fmap_maybe(f, out)
|
||||
return __string_format('%.0f Mhz', f / n)
|
||||
end
|
||||
|
||||
M.topology_to_cpu_map = function(topology)
|
||||
local r = {}
|
||||
for group_id, group in pairs(topology) do
|
||||
for _, core in pairs(group) do
|
||||
for _, cpu in pairs(core.cpus) do
|
||||
r[cpu.lgl_cpu_id] = group_id
|
||||
end
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
M.read_ave_freqs = function(topology, cpu_group_map)
|
||||
M.read_freq = function()
|
||||
-- NOTE: Using the builtin conky functions for getting cpu freq seems to make
|
||||
-- the entire loop jittery due to high variance latency. Querying
|
||||
-- scaling_cur_freq in sysfs seems to do the same thing. It appears lscpu
|
||||
-- (which queries /proc/cpuinfo) is much faster and doesn't have this jittery
|
||||
-- problem.
|
||||
i_o.assert_exe_exists('lscpu')
|
||||
local out = i_o.execute_cmd('lscpu -p=MHZ')
|
||||
local init_freqs = function(v)
|
||||
local r = {}
|
||||
for group_id, _ in pairs(topology) do
|
||||
r[group_id] = v
|
||||
end
|
||||
return r
|
||||
end
|
||||
if out == nil then
|
||||
return init_freqs('N/A')
|
||||
else
|
||||
local ave_freqs = init_freqs(0)
|
||||
local cpu_id = 1
|
||||
for s in __string_gmatch(out, '(%d+%.%d+)') do
|
||||
local group_id = cpu_group_map[cpu_id]
|
||||
ave_freqs[group_id] = ave_freqs[group_id] + __tonumber(s)
|
||||
cpu_id = cpu_id + 1
|
||||
end
|
||||
for group_id, _ in pairs(ave_freqs) do
|
||||
ave_freqs[group_id] =
|
||||
__string_format(
|
||||
'%.0f Mhz',
|
||||
ave_freqs[group_id] / (group_id * #topology[group_id])
|
||||
)
|
||||
end
|
||||
return ave_freqs
|
||||
end
|
||||
return pure.maybe('N/A', match_freq, i_o.execute_cmd('lscpu -p=MHZ'))
|
||||
end
|
||||
|
||||
M.get_hwp_paths = function(topology)
|
||||
M.get_hwp_paths = function()
|
||||
-- ASSUME this will never fail
|
||||
return pure.map_n(
|
||||
function(i)
|
||||
|
@ -334,7 +278,7 @@ M.get_hwp_paths = function(topology)
|
|||
.. (i - 1)
|
||||
.. '/cpufreq/energy_performance_preference'
|
||||
end,
|
||||
M.get_cpu_number(topology)
|
||||
M.get_cpu_number()
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -362,33 +306,35 @@ M.read_hwp = function(hwp_paths)
|
|||
return mixed and 'Mixed' or (HWP_MAP[hwp_pref] or 'Unknown')
|
||||
end
|
||||
|
||||
M.init_cpu_loads = function(topo)
|
||||
local ncpus = M.get_cpu_number(topo)
|
||||
M.init_cpu_loads = function()
|
||||
local m = get_core_mappings()
|
||||
local cpu_loads = {}
|
||||
for lgl_cpu_id = 1, ncpus do
|
||||
cpu_loads[lgl_cpu_id] = {
|
||||
for cpu_id, core in pairs(m) do
|
||||
cpu_loads[cpu_id] = {
|
||||
active_prev = 0,
|
||||
total_prev = 0,
|
||||
percent_active = 0,
|
||||
conky_core_idx = core.conky_core_idx,
|
||||
conky_thread_id = core.conky_thread_id,
|
||||
}
|
||||
end
|
||||
return cpu_loads
|
||||
end
|
||||
|
||||
M.read_cpu_loads = function(cpu_loads)
|
||||
local ncpus = #cpu_loads
|
||||
local iter = io.lines('/proc/stat')
|
||||
iter() -- ignore first line
|
||||
for lgl_cpu_id = 1, #cpu_loads do
|
||||
for i = 1, ncpus do
|
||||
local ln = iter()
|
||||
local user, system, idle =
|
||||
__string_match(ln, '%d+ (%d+) %d+ (%d+) (%d+)', 4)
|
||||
local user, system, idle = __string_match(ln, '(%d+) %d+ (%d+) (%d+)', 5)
|
||||
local active = user + system
|
||||
local total = active + idle
|
||||
local cpu = cpu_loads[lgl_cpu_id]
|
||||
if total > cpu.total_prev then -- guard against 1/0 errors
|
||||
cpu.percent_active = (active - cpu.active_prev) / (total - cpu.total_prev)
|
||||
cpu.active_prev = active
|
||||
cpu.total_prev = total
|
||||
local c = cpu_loads[i]
|
||||
if total > c.total_prev then -- guard against 1/0 errors
|
||||
c.percent_active = (active - c.active_prev) / (total - c.total_prev)
|
||||
c.active_prev = active
|
||||
c.total_prev = total
|
||||
end
|
||||
end
|
||||
return cpu_loads
|
||||
|
|
257
src/validate.lua
257
src/validate.lua
|
@ -1,257 +0,0 @@
|
|||
local M = {}
|
||||
|
||||
local err = require 'err'
|
||||
local i_o = require 'i_o'
|
||||
local pure = require 'pure'
|
||||
local sys = require 'sys'
|
||||
|
||||
M.assert_non_nil = function(what, x)
|
||||
i_o.assertf(x ~= nil, '%s: %f must not be nil', what, x)
|
||||
end
|
||||
|
||||
M.assert_number = function(what, x)
|
||||
i_o.assertf(err.get_type(x) == 'number', '%s: \'%s\' must be a number', what, x)
|
||||
end
|
||||
|
||||
M.assert_integer = function(what, x)
|
||||
M.assert_number(what, x)
|
||||
i_o.assertf(math.fmod(x, 1) == 0, '%s: \'%s\' must be an integer', what, x)
|
||||
end
|
||||
|
||||
M.assert_less_than = function(what, x, upper)
|
||||
M.assert_number(what, x)
|
||||
i_o.assertf(
|
||||
x < upper,
|
||||
'%s: %f must be less than %f',
|
||||
what, x, upper
|
||||
)
|
||||
end
|
||||
|
||||
M.assert_greater_than = function(what, x, lower)
|
||||
M.assert_number(what, x)
|
||||
i_o.assertf(
|
||||
x > lower,
|
||||
'%s: %f must be greater than %f',
|
||||
what, x, lower
|
||||
)
|
||||
end
|
||||
|
||||
M.assert_less_than_or_eq = function(what, x, upper)
|
||||
M.assert_number(what, x)
|
||||
i_o.assertf(
|
||||
x <= upper,
|
||||
'%s: %f must be less than or equal to %f',
|
||||
what, x, upper
|
||||
)
|
||||
end
|
||||
|
||||
M.assert_greater_than_or_eq = function(what, x, lower)
|
||||
M.assert_number(what, x)
|
||||
i_o.assertf(
|
||||
x >= lower,
|
||||
'%s: %f must be greater than or equal to %f',
|
||||
what, x, lower
|
||||
)
|
||||
end
|
||||
|
||||
M.assert_positive = function(what, x)
|
||||
M.assert_greater_than(what, x, 0)
|
||||
end
|
||||
|
||||
M.assert_between = function(what, x, lower, upper)
|
||||
M.assert_number(what, x)
|
||||
i_o.assertf(
|
||||
x >= lower and x <= upper,
|
||||
'%s: %f must be between %f and %f',
|
||||
what, x, lower, upper
|
||||
)
|
||||
end
|
||||
|
||||
M.assert_member = function(what, x, seq)
|
||||
M.assert_non_nil(what, x)
|
||||
local xs = pure.collapse(pure.keys(seq), ', ')
|
||||
i_o.assertf(pure.member(x, seq), '%s: %s is not one of %s', what, x, xs)
|
||||
end
|
||||
|
||||
M.assert_unit_fraction = function(what, x)
|
||||
M.assert_number(what, x)
|
||||
M.assert_between(what, x, 0, 1)
|
||||
end
|
||||
|
||||
M.assert_color = function(what, x)
|
||||
M.assert_number(what, x)
|
||||
M.assert_between(what, x, 0, 0xffffff)
|
||||
end
|
||||
|
||||
M.assert_color_alpha = function(what, x)
|
||||
M.assert_color(what..'.color', x.color)
|
||||
M.assert_unit_fraction(what..'.alpha', x.alpha)
|
||||
end
|
||||
|
||||
M.assert_color_stop = function(what, x)
|
||||
M.assert_color(what..'.color', x.color)
|
||||
M.assert_unit_fraction(what..'.stop', x.stop)
|
||||
end
|
||||
|
||||
M.assert_color_stop_alpha = function(what, x)
|
||||
M.assert_color(what..'.color', x.color)
|
||||
M.assert_unit_fraction(what..'.stop', x.stop)
|
||||
M.assert_unit_fraction(what..'.alpha', x.alpha)
|
||||
end
|
||||
|
||||
M.assert_file_exists = function(what, path)
|
||||
i_o.assertf(i_o.file_exists(path), '%s: %s does not exist', what, path)
|
||||
end
|
||||
|
||||
M.assert_file_readable = function(what, path)
|
||||
M.assert_file_exists(what, path)
|
||||
i_o.assertf(i_o.file_readable(path), '%s: %s is not readable', what, path)
|
||||
end
|
||||
|
||||
M.assert_valid_pattern = function(what, pat)
|
||||
local t = pat.type
|
||||
if t == "RGB" then
|
||||
M.assert_color(what, pat.data)
|
||||
elseif t == "RGBA" then
|
||||
M.assert_color_alpha(what, pat.data)
|
||||
elseif t == "GradientRGB" then
|
||||
for _, x in pairs(pat.data) do
|
||||
M.assert_color_stop(what, x)
|
||||
end
|
||||
elseif t == "GradientRGBA" then
|
||||
for _, x in pairs(pat.data) do
|
||||
M.assert_color_stop_alpha(what, x)
|
||||
end
|
||||
else
|
||||
i_o.assertf(nil, '%s: pattern has invalid type \'%s\'', what, t)
|
||||
end
|
||||
end
|
||||
|
||||
M.validate_plot_ticks_y = function(what, x)
|
||||
M.assert_greater_than(what..'.plot.ticks_y', x, 2)
|
||||
end
|
||||
|
||||
-- TODO make spacing parameters aware of thickness, right now they start at the
|
||||
-- centry of lines, circles, etc.
|
||||
|
||||
local validate_filesystem = function(x)
|
||||
for _, f in pairs(x.fs_paths) do
|
||||
M.assert_file_exists('filesystem.fs_paths', f.path)
|
||||
end
|
||||
end
|
||||
|
||||
local validate_graphics = function(x)
|
||||
-- NOTE don't check that the bus exists because some optimus laptops 'turn
|
||||
-- off' by pulling the entire kernel module and making the bus go poof
|
||||
M.validate_plot_ticks_y('graphics.geometry', x.geometry.plot.ticks_y)
|
||||
end
|
||||
|
||||
local validate_memory = function(x)
|
||||
M.validate_plot_ticks_y('memory.geometry', x.geometry.plot.ticks_y)
|
||||
end
|
||||
|
||||
local validate_network = function(x)
|
||||
M.validate_plot_ticks_y('network.geometry', x.geometry.plot.ticks_y)
|
||||
end
|
||||
|
||||
local validate_pacman = function(_)
|
||||
-- nothing to check here
|
||||
end
|
||||
|
||||
local validate_power = function(x)
|
||||
M.assert_file_readable('power.battery', sys.SYSFS_POWER..'/'..x.battery)
|
||||
for _, f in pairs(x.rapl_specs) do
|
||||
M.assert_file_readable('power.rapl_specs', sys.SYSFS_RAPL..'/'..f.address)
|
||||
end
|
||||
M.assert_greater_than('power.genometry.plot.ticks_y', x.geometry.plot.ticks_y, 2)
|
||||
end
|
||||
|
||||
local validate_processor = function(x)
|
||||
-- NOTE need to check that the processor groups actually exist after querying
|
||||
-- the cpu in the module
|
||||
M.assert_greater_than('processor.genometry.plot.ticks_y', x.geometry.plot.ticks_y, 2)
|
||||
for _, c in pairs(x.core_groups) do
|
||||
M.assert_greater_than('processor.core_groups.[].rows', c.rows, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local validate_readwrite = function(x)
|
||||
M.validate_plot_ticks_y('readwrite.geometry', x.geometry.plot.ticks_y)
|
||||
end
|
||||
|
||||
local validate_system = function(_)
|
||||
-- nothing to check here
|
||||
end
|
||||
|
||||
M.validate_config = function(config)
|
||||
local boot = config.bootstrap
|
||||
M.assert_positive('bootstrap.update_interval', boot.update_interval)
|
||||
M.assert_positive('bootstrap.dimensions.x', boot.dimensions.x)
|
||||
M.assert_positive('bootstrap.dimensions.y', boot.dimensions.y)
|
||||
|
||||
local font = config.theme.font.sizes
|
||||
local _font = 'theme.font.sizes'
|
||||
M.assert_positive(_font..'normal', font.normal)
|
||||
M.assert_positive(_font..'plot_label', font.plot_label)
|
||||
M.assert_positive(_font..'table', font.table)
|
||||
M.assert_positive(_font..'header', font.header)
|
||||
|
||||
local geo = config.theme.geometry
|
||||
local _geo = 'theme.geometry.'
|
||||
M.assert_greater_than(_geo..'plot.seconds', geo.plot.seconds, 10)
|
||||
M.assert_greater_than(_geo..'plot.ticks_x', geo.plot.ticks_x, 4)
|
||||
M.assert_greater_than(_geo..'table.name_chars', geo.table.name_chars, 5)
|
||||
M.assert_greater_than(_geo..'table.row_spacing', geo.table.row_spacing, 10)
|
||||
|
||||
local pat = config.theme.patterns
|
||||
local _pat = 'theme.pattern.'
|
||||
M.assert_valid_pattern(_pat..'header', pat.header)
|
||||
M.assert_valid_pattern(_pat..'panel.bg', pat.panel.bg)
|
||||
M.assert_valid_pattern(_pat..'text.active', pat.text.active)
|
||||
M.assert_valid_pattern(_pat..'text.inactive', pat.text.inactive)
|
||||
M.assert_valid_pattern(_pat..'text.critical', pat.text.critical)
|
||||
M.assert_valid_pattern(_pat..'border', pat.border)
|
||||
M.assert_valid_pattern(_pat..'plot.grid', pat.plot.grid)
|
||||
M.assert_valid_pattern(_pat..'plot.outline', pat.plot.outline)
|
||||
M.assert_valid_pattern(_pat..'plot.data.border', pat.plot.data.border)
|
||||
M.assert_valid_pattern(_pat..'plot.data.fill', pat.plot.data.fill)
|
||||
M.assert_valid_pattern(_pat..'indicator.bg', pat.indicator.bg)
|
||||
M.assert_valid_pattern(_pat..'indicator.fg.active', pat.indicator.fg.active)
|
||||
M.assert_valid_pattern(_pat..'indicator.fg.critical', pat.indicator.fg.critical)
|
||||
|
||||
for _, panel in pairs(config.layout.panels) do
|
||||
if type(panel) == "table" then
|
||||
for _, column in pairs(panel.columns) do
|
||||
if type(column) == "table" then
|
||||
for _, block in pairs(column.blocks) do
|
||||
if type(block) == "table" then
|
||||
local t = block.type
|
||||
local d = block.data
|
||||
if t == "filesystem" then
|
||||
validate_filesystem(d)
|
||||
elseif t == "graphics" then
|
||||
validate_graphics(d)
|
||||
elseif t == "memory" then
|
||||
validate_memory(d)
|
||||
elseif t == "network" then
|
||||
validate_network(d)
|
||||
elseif t == "pacman" then
|
||||
validate_pacman(d)
|
||||
elseif t == "power" then
|
||||
validate_power(d)
|
||||
elseif t == "processor" then
|
||||
validate_processor(d)
|
||||
elseif t == "readwrite" then
|
||||
validate_readwrite(d)
|
||||
elseif t == "system" then
|
||||
validate_system(d)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
|
@ -130,7 +130,6 @@ M.get_delta_y = function(y_align, font)
|
|||
local fe = set_font_extents(font)
|
||||
local descents = {
|
||||
top = fe.height,
|
||||
-- TODO this 92 thing shouldn't be hardcoded (probably)
|
||||
center = 0.92 * fe.height * 0.5 - fe.descent,
|
||||
bottom = -fe.descent
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue