diff --git a/internal/dis/component/control/info/components.go b/internal/dis/component/control/info/components.go new file mode 100644 index 0000000..a3f8900 --- /dev/null +++ b/internal/dis/component/control/info/components.go @@ -0,0 +1,27 @@ +package info + +import ( + _ "embed" + "net/http" + "time" + + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static" + "github.com/FAU-CDI/wisski-distillery/pkg/lazy" +) + +//go:embed "html/components.html" +var componentsTemplateString string +var componentsTemplate = static.AssetsComponentsIndex.MustParse(componentsTemplateString) + +type componentsPageContext struct { + Time time.Time + + Analytics lazy.PoolAnalytics +} + +func (info *Info) componentsPageAPI(r *http.Request) (cp componentsPageContext, err error) { + cp.Analytics = *info.Analytics + cp.Time = time.Now().UTC() + + return +} diff --git a/internal/dis/component/control/info/html/components.html b/internal/dis/component/control/info/html/components.html new file mode 100644 index 0000000..a35e5f6 --- /dev/null +++ b/internal/dis/component/control/info/html/components.html @@ -0,0 +1,143 @@ + + + + + Distillery Control Page - Components Page + {{ CSS }} + + + +
+

Distillery Control Page - Components Page

+ Generated at {{ .Time.Format "2006-01-02T15:04:05Z07:00" }} +

+ Control > + Components +

+
+ +
+
+
+

Components

+
+ + {{ range $name, $comp := .Analytics.Components }} +
+
+ +
+ + + + + + + + {{ range .Groups }} + + + + + {{ end }} + {{ range $name, $comp := .CFields }} + + + + + + {{ end }} + {{ range $name, $iface := .IFields }} + + + + + + {{ end }} + {{ range $name, $sig := $comp.Methods }} + + + + + + {{ end }} + +
+ {{ $name }} +
+ Implements + + {{ . }}
+
Component Pointer + {{ $name }} + + {{ $comp }} +
Interface Slice + {{ $name }} + + []{{ $iface }} +
+ Method + + {{ $name }} + + {{ $sig }} +
+
+
+
+
+ {{ end }} + +
+

Interfaces

+
+ + {{ range $name, $group := .Analytics.Groups }} +
+
+ +
+ + + + + + + + {{ range $name, $sig := $group.Methods }} + + + + + + {{ end }} + {{ range $group.Components }} + + + + + {{ end }} + +
+ {{ $name }} +
+ Method + + {{ $name }} + + {{ $sig }} +
+ Implemented By + + {{ . }} +
+
+
+
+ + {{ end }} +
+ + {{ JS }} + \ No newline at end of file diff --git a/internal/dis/component/control/info/html/index.html b/internal/dis/component/control/info/html/index.html index 439f794..7d031c5 100644 --- a/internal/dis/component/control/info/html/index.html +++ b/internal/dis/component/control/info/html/index.html @@ -13,6 +13,9 @@

Control

+

+ Components +

diff --git a/internal/dis/component/control/info/info.go b/internal/dis/component/control/info/info.go index 8903a43..19724b3 100644 --- a/internal/dis/component/control/info/info.go +++ b/internal/dis/component/control/info/info.go @@ -10,12 +10,15 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances" "github.com/FAU-CDI/wisski-distillery/pkg/httpx" + "github.com/FAU-CDI/wisski-distillery/pkg/lazy" "github.com/tkw1536/goprogram/stream" ) type Info struct { component.Base + Analytics *lazy.PoolAnalytics + Exporter *exporter.Exporter Instances *instances.Instances SnapshotsLog *logger.Logger @@ -41,6 +44,12 @@ func (info *Info) Handler(route string, context context.Context, io stream.IOStr Template: indexTemplate, }) + // add a handler for the component page + mux.Handle(route+"components", httpx.HTMLHandler[componentsPageContext]{ + Handler: info.componentsPageAPI, + Template: componentsTemplate, + }) + // add a handler for the instance page mux.Handle(route+"instance/", httpx.HTMLHandler[instancePageContext]{ Handler: info.instancePageAPI, diff --git a/internal/dis/component/control/static/assets.go b/internal/dis/component/control/static/assets.go index a48e3e8..6afe929 100644 --- a/internal/dis/component/control/static/assets.go +++ b/internal/dis/component/control/static/assets.go @@ -22,7 +22,7 @@ type Assets struct { Styles string // tags inserted by the asset } -//go:generate node build.mjs HomeHome ControlIndex ControlInstance +//go:generate node build.mjs HomeHome ComponentsIndex ControlIndex ControlInstance // MustParse parses a new template from the given source // and registers the Asset functions to it. diff --git a/internal/dis/component/control/static/assets_dist.go b/internal/dis/component/control/static/assets_dist.go index a1ad92d..c59144e 100644 --- a/internal/dis/component/control/static/assets_dist.go +++ b/internal/dis/component/control/static/assets_dist.go @@ -8,6 +8,12 @@ var AssetsHomeHome = Assets{ Styles: ``, } +// AssetsComponentsIndex contains assets for the 'ComponentsIndex' entrypoint. +var AssetsComponentsIndex = Assets{ + Scripts: ``, + Styles: ``, +} + // AssetsControlIndex contains assets for the 'ControlIndex' entrypoint. var AssetsControlIndex = Assets{ Scripts: ``, diff --git a/internal/dis/component/control/static/dist/ComponentsIndex.38d394c2.css b/internal/dis/component/control/static/dist/ComponentsIndex.38d394c2.css new file mode 100644 index 0000000..e69de29 diff --git a/internal/dis/component/control/static/dist/ComponentsIndex.38d394c2.js b/internal/dis/component/control/static/dist/ComponentsIndex.38d394c2.js new file mode 100644 index 0000000..e69de29 diff --git a/internal/dis/component/control/static/src/entry/ComponentsIndex/index.css b/internal/dis/component/control/static/src/entry/ComponentsIndex/index.css new file mode 100644 index 0000000..e69de29 diff --git a/internal/dis/component/control/static/src/entry/ComponentsIndex/index.ts b/internal/dis/component/control/static/src/entry/ComponentsIndex/index.ts new file mode 100644 index 0000000..b0acb4c --- /dev/null +++ b/internal/dis/component/control/static/src/entry/ComponentsIndex/index.ts @@ -0,0 +1 @@ +// nothing for now \ No newline at end of file diff --git a/internal/dis/distillery.go b/internal/dis/distillery.go index a9974ea..fa4303e 100644 --- a/internal/dis/distillery.go +++ b/internal/dis/distillery.go @@ -137,6 +137,8 @@ func (dis *Distillery) allComponents() []initFunc { manual(func(resolver *resolver.Resolver) { resolver.RefreshInterval = time.Minute }), - auto[*info.Info], + manual(func(info *info.Info) { + info.Analytics = &dis.pool.Analytics + }), } } diff --git a/pkg/lazy/pool.go b/pkg/lazy/pool.go index 07943c7..d647f7a 100644 --- a/pkg/lazy/pool.go +++ b/pkg/lazy/pool.go @@ -2,7 +2,6 @@ package lazy import ( "reflect" - "sync" ) // Pool represents a pool of laziliy initialized and potentially referencing Component instances. @@ -19,6 +18,9 @@ type Pool[Component any, InitParams any] struct { // Init is called on every component to be initialized. Init func(Component, InitParams) Component + // Analytics are written on the first retrieval operation on this Pool + Analytics PoolAnalytics + all Lazy[[]Component] } @@ -37,12 +39,16 @@ func (p *Pool[Component, InitParams]) All(Params InitParams, All func(context *P } return p.Init(c, Params) }, - cache: make(map[string]Component), + metaCache: make(map[reflect.Type]meta[Component]), + cache: make(map[string]Component), } // and process them all all := context.all(context) - context.Process(all) + context.process(all) + + // write out analytics + context.anal(&p.Analytics) return all }) } @@ -56,7 +62,7 @@ type PoolContext[Component any] struct { // function to return all components - metaCache sync.Map // Map[string]meta[Component] + metaCache map[reflect.Type]meta[Component] cache map[string]Component // cached components queue []delayedInit[Component] // init queue } @@ -67,7 +73,7 @@ type delayedInit[Component any] struct { } // Process processes all components in the queue -func (p *PoolContext[Component]) Process(all []Component) { +func (p *PoolContext[Component]) process(all []Component) { index := 0 for len(p.queue) > index { p.queue[index].Run(all) @@ -97,7 +103,7 @@ func (di *delayedInit[Component]) Run(all []Component) { // The init function may be nil, indicating that no additional initialization is required. func Make[Component any, ConcreteComponent any](context *PoolContext[Component], init func(component ConcreteComponent)) ConcreteComponent { // get a description of the type - cd := getMeta[Component, ConcreteComponent](&context.metaCache) + cd := getMeta[Component, ConcreteComponent](context.metaCache) // if an instance already exists, return it! if instance, ok := context.cache[cd.Name]; ok { diff --git a/pkg/lazy/pool_anal.go b/pkg/lazy/pool_anal.go new file mode 100644 index 0000000..806994d --- /dev/null +++ b/pkg/lazy/pool_anal.go @@ -0,0 +1,93 @@ +package lazy + +import ( + "reflect" + + "github.com/tkw1536/goprogram/lib/collection" + "golang.org/x/exp/slices" +) + +type PoolAnalytics struct { + Components map[string]*PoolAnalyticsComponent + Groups map[string]*PoolAnalyticsGroup +} + +type PoolAnalyticsComponent struct { + Type string // Type name + Groups []string // groups this is contained in + + CFields map[string]string // fields with type C for which C implements component + IFields map[string]string // fields []I where I is an interface that implements component + + Methods map[string]string // Method signatures of type +} +type PoolAnalyticsGroup struct { + Type string // Type name + Components []string // Components of this Type + + Methods map[string]string // Method signatures of this interface +} + +// anal writes analytics about this context to anal +func (context *PoolContext[Component]) anal(anal *PoolAnalytics) { + anal.Components = make(map[string]*PoolAnalyticsComponent, len(context.metaCache)) + anal.Groups = make(map[string]*PoolAnalyticsGroup) + + // collect all the pointers, and setup the anal.Components map! + tpPointers := make([]reflect.Type, 0, len(context.metaCache)) + for _, meta := range context.metaCache { + tp := reflect.PointerTo(meta.Elem) + tpPointers = append(tpPointers, tp) + + mcount := tp.NumMethod() + + anal.Components[meta.Name] = &PoolAnalyticsComponent{ + Groups: make([]string, 0), + Methods: make(map[string]string, mcount), + } + for i := 0; i < mcount; i++ { + method := tp.Method(i) + anal.Components[meta.Name].Methods[method.Name] = method.Type.String() + } + } + + // take all of the components out of the cache + for _, meta := range context.metaCache { + anal.Components[meta.Name].Type = meta.Name + anal.Components[meta.Name].CFields = collection.MapValues(meta.CFields, func(key string, tp reflect.Type) string { + return nameOf(tp.Elem()) + }) + + anal.Components[meta.Name].IFields = collection.MapValues(meta.IFields, func(key string, iface reflect.Type) string { + name := nameOf(iface) + + if _, ok := anal.Groups[name]; !ok { + types := collection.FilterClone(tpPointers, func(tp reflect.Type) bool { + return tp.AssignableTo(iface) + }) + + anal.Groups[name] = &PoolAnalyticsGroup{ + Type: name, + Components: collection.MapSlice(types, func(tp reflect.Type) string { + cname := nameOf(tp.Elem()) + anal.Components[cname].Groups = append(anal.Components[cname].Groups, name) + return cname + }), + } + + mcount := iface.NumMethod() + anal.Groups[name].Methods = make(map[string]string, mcount) + for i := 0; i < mcount; i++ { + method := iface.Method(i) + anal.Groups[name].Methods[method.Name] = method.Type.String() + } + } + + return name + }) + } + + for _, comp := range anal.Components { + slices.Sort(comp.Groups) + } +} diff --git a/pkg/lazy/pool_meta.go b/pkg/lazy/pool_meta.go index 54539e7..a548c3a 100644 --- a/pkg/lazy/pool_meta.go +++ b/pkg/lazy/pool_meta.go @@ -2,19 +2,18 @@ package lazy import ( "reflect" - "sync" "github.com/tkw1536/goprogram/lib/collection" "github.com/tkw1536/goprogram/lib/reflectx" ) // getMeta gets the component belonging to a component type -func getMeta[Component any, ConcreteComponent any](metaCache *sync.Map) meta[Component] { +func getMeta[Component any, ConcreteComponent any](cache map[reflect.Type]meta[Component]) meta[Component] { tp := reflectx.TypeOf[ConcreteComponent]() // we already have a m => return it - if m, ok := metaCache.Load(tp); ok { - return m.(meta[Component]) + if m, ok := cache[tp]; ok { + return m } // create a new m @@ -22,7 +21,7 @@ func getMeta[Component any, ConcreteComponent any](metaCache *sync.Map) meta[Com m.init(tp) // store it in the cache - metaCache.Store(tp, m) + cache[tp] = m return m } @@ -43,8 +42,8 @@ func (m *meta[Component]) init(tp reflect.Type) { panic("GetMeta: Type (" + tp.String() + ") must be backed by a pointer to slice") } - m.Name = tp.Elem().PkgPath() + "." + tp.Elem().Name() m.Elem = tp.Elem() + m.Name = nameOf(m.Elem) m.CFields = make(map[string]reflect.Type) m.IFields = make(map[string]reflect.Type) @@ -69,6 +68,10 @@ func (m *meta[Component]) init(tp reflect.Type) { } } +func nameOf(tp reflect.Type) string { + return tp.PkgPath() + "." + tp.Name() +} + // New creates a new ComponentDescription func (m meta[Component]) New() Component { return reflect.New(m.Elem).Interface().(Component)