go-hep.org/x/hep@v0.38.1/fwk/dflow.go (about) 1 // Copyright ©2017 The go-hep Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package fwk 6 7 import ( 8 "fmt" 9 "os" 10 "reflect" 11 "sort" 12 "strings" 13 14 "go-hep.org/x/hep/fwk/utils/tarjan" 15 "gonum.org/v1/gonum/graph/encoding" 16 "gonum.org/v1/gonum/graph/encoding/dot" 17 "gonum.org/v1/gonum/graph/simple" 18 ) 19 20 type node struct { 21 in map[string]reflect.Type 22 out map[string]reflect.Type 23 } 24 25 func newNode() *node { 26 return &node{ 27 in: make(map[string]reflect.Type), 28 out: make(map[string]reflect.Type), 29 } 30 } 31 32 // dflowsvc models and describes the runtime data-flow and (data) dependencies between 33 // components as declared during configuration. 34 type dflowsvc struct { 35 SvcBase 36 nodes map[string]*node 37 edges map[string]reflect.Type 38 39 dotfile string // path to a DOT file where to dump the data dependency graph. 40 } 41 42 func (svc *dflowsvc) Configure(ctx Context) error { 43 return nil 44 } 45 46 func (svc *dflowsvc) StartSvc(ctx Context) error { 47 var err error 48 49 // sort node-names for reproducibility 50 nodenames := make([]string, 0, len(svc.nodes)) 51 for n := range svc.nodes { 52 nodenames = append(nodenames, n) 53 } 54 sort.Strings(nodenames) 55 56 // - make sure all input keys of components are available 57 // as output keys of a task 58 // - also detect whether a key is labeled as an out-port 59 // by 2 different components 60 out := make(map[string]string) // outport-name -> producer-name 61 for _, tsk := range nodenames { 62 node := svc.nodes[tsk] 63 for k := range node.out { 64 n, dup := out[k] 65 if dup { 66 return fmt.Errorf("%s: component [%s] already declared port [%s] as its output (current=%s)", 67 svc.Name(), n, k, tsk, 68 ) 69 } 70 out[k] = tsk 71 } 72 } 73 74 for _, tsk := range nodenames { 75 node := svc.nodes[tsk] 76 for k := range node.in { 77 _, ok := out[k] 78 if !ok { 79 return fmt.Errorf("%s: component [%s] declared port [%s] as input but NO KNOWN producer", 80 svc.Name(), tsk, k, 81 ) 82 } 83 } 84 } 85 86 // detect cycles. 87 graph := make(map[any][]any) 88 for _, n := range nodenames { 89 node := svc.nodes[n] 90 graph[n] = []any{} 91 for in := range node.in { 92 for _, o := range nodenames { 93 if o == n { 94 continue 95 } 96 onode := svc.nodes[o] 97 connected := false 98 for out := range onode.out { 99 if in == out { 100 connected = true 101 break 102 } 103 } 104 if connected { 105 graph[n] = append(graph[n], o) 106 } 107 } 108 } 109 } 110 111 cycles := tarjan.Connections(graph) 112 if len(cycles) > 0 { 113 msg := ctx.Msg() 114 ncycles := 0 115 for _, cycle := range cycles { 116 if len(cycle) > 1 { 117 ncycles++ 118 msg.Errorf("cycle detected: %v\n", cycle) 119 } 120 } 121 s := "" 122 if ncycles > 1 { 123 s = "s" 124 } 125 if ncycles > 0 { 126 return fmt.Errorf("%s: cycle%s detected: %d", svc.Name(), s, ncycles) 127 } 128 } 129 130 if svc.dotfile != "" { 131 err = svc.dumpGraph() 132 if err != nil { 133 return err 134 } 135 } 136 return err 137 } 138 139 func (svc *dflowsvc) StopSvc(ctx Context) error { 140 return nil 141 } 142 143 func (svc *dflowsvc) keys() []string { 144 keys := make([]string, 0, len(svc.edges)) 145 for k := range svc.edges { 146 keys = append(keys, k) 147 } 148 return keys 149 } 150 151 func (svc *dflowsvc) addInNode(tsk string, name string, t reflect.Type) error { 152 node, ok := svc.nodes[tsk] 153 if !ok { 154 node = newNode() 155 svc.nodes[tsk] = node 156 } 157 _, ok = node.in[name] 158 if ok { 159 return fmt.Errorf( 160 "fwk.DeclInPort: component [%s] already declared in-port with name [%s]", 161 tsk, 162 name, 163 ) 164 } 165 166 node.in[name] = t 167 edgetyp, dup := svc.edges[name] 168 if dup { 169 // make sure types match 170 if edgetyp != t { 171 type elemT struct { 172 port string // in/out 173 task string // task which defined the port 174 typ reflect.Type 175 } 176 cont := []elemT{} 177 nodenames := make([]string, 0, len(svc.nodes)) 178 for tskname := range svc.nodes { 179 nodenames = append(nodenames, tskname) 180 } 181 sort.Strings(nodenames) 182 for _, tskname := range nodenames { 183 node := svc.nodes[tskname] 184 for k, in := range node.in { 185 if k != name { 186 continue 187 } 188 cont = append(cont, 189 elemT{ 190 port: "in ", 191 task: tskname, 192 typ: in, 193 }, 194 ) 195 } 196 for k, out := range node.out { 197 if k != name { 198 continue 199 } 200 cont = append(cont, 201 elemT{ 202 port: "out", 203 task: tskname, 204 typ: out, 205 }, 206 ) 207 } 208 } 209 var o strings.Builder 210 fmt.Fprintf(&o, "fwk.DeclInPort: detected type inconsistency for port [%s]:\n", name) 211 for _, c := range cont { 212 fmt.Fprintf(&o, " component=%q port=%s type=%v\n", c.task, c.port, c.typ) 213 } 214 return fmt.Errorf("%v", o.String()) 215 } 216 } 217 218 svc.edges[name] = t 219 return nil 220 } 221 222 func (svc *dflowsvc) addOutNode(tsk string, name string, t reflect.Type) error { 223 node, ok := svc.nodes[tsk] 224 if !ok { 225 node = newNode() 226 svc.nodes[tsk] = node 227 } 228 _, ok = node.out[name] 229 if ok { 230 return fmt.Errorf( 231 "fwk.DeclOutPort: component [%s] already declared out-port with name [%s]", 232 tsk, 233 name, 234 ) 235 } 236 237 node.out[name] = t 238 239 edgetyp, dup := svc.edges[name] 240 if dup { 241 // edge already exists 242 // loop over nodes, find out who already defined that edge 243 nodenames := make([]string, 0, len(svc.nodes)) 244 for tskname := range svc.nodes { 245 nodenames = append(nodenames, tskname) 246 } 247 sort.Strings(nodenames) 248 for _, duptsk := range nodenames { 249 dupnode := svc.nodes[duptsk] 250 if duptsk == tsk { 251 continue 252 } 253 for out := range dupnode.out { 254 if out == name { 255 return fmt.Errorf( 256 "fwk.DeclOutPort: component [%s] already declared out-port with name [%s (type=%v)].\nfwk.DeclOutPort: component [%s] is trying to add a duplicate out-port [%s (type=%v)]", 257 duptsk, 258 name, 259 edgetyp, 260 tsk, 261 name, 262 t, 263 ) 264 } 265 } 266 } 267 } 268 svc.edges[name] = t 269 return nil 270 } 271 272 type nodeFlow struct { 273 simple.Node 274 attrs []encoding.Attribute 275 } 276 277 func (n *nodeFlow) Attributes() []encoding.Attribute { 278 return n.attrs 279 } 280 281 func (svc *dflowsvc) dumpGraph() error { 282 var err error 283 gr := simple.NewDirectedGraph() 284 285 quote := func(s string) string { 286 return fmt.Sprintf("%q", s) 287 } 288 289 id := int64(0) 290 ids := make(map[string]*nodeFlow, len(svc.edges)+len(svc.nodes)) 291 292 { 293 keys := make([]string, 0, len(svc.edges)) 294 for edge := range svc.edges { 295 keys = append(keys, edge) 296 } 297 sort.Strings(keys) 298 299 for _, edge := range keys { 300 id++ 301 node := &nodeFlow{ 302 simple.Node(id), 303 []encoding.Attribute{ 304 {Key: `"node"`, Value: `"data"`}, 305 {Key: `"label"`, Value: quote(edge)}, 306 }, 307 } 308 ids["data-"+edge] = node 309 gr.AddNode(node) 310 } 311 312 keys = keys[:0] 313 for name := range svc.nodes { 314 keys = append(keys, name) 315 } 316 sort.Strings(keys) 317 318 for _, name := range keys { 319 id++ 320 node := &nodeFlow{ 321 simple.Node(id), 322 []encoding.Attribute{ 323 {Key: `"node"`, Value: `"task"`}, 324 {Key: `"shape"`, Value: `"component"`}, 325 {Key: `"label"`, Value: quote(name)}, 326 }, 327 } 328 ids["task-"+name] = node 329 gr.AddNode(node) 330 } 331 } 332 333 for name, node := range svc.nodes { 334 for in := range node.in { 335 from := ids["data-"+in] 336 to := ids["task-"+name] 337 gr.SetEdge(simple.Edge{ 338 F: from, 339 T: to, 340 }) 341 } 342 343 for out := range node.out { 344 from := ids["task-"+name] 345 to := ids["data-"+out] 346 gr.SetEdge(simple.Edge{ 347 F: from, 348 T: to, 349 }) 350 } 351 } 352 353 out, err := dot.Marshal(gr, "dataflow", "", " ") 354 if err != nil { 355 return fmt.Errorf("could not marshal dataflow to 'dot' format: %w", err) 356 } 357 out = append(out, '\n') 358 359 err = os.WriteFile(svc.dotfile, out, 0644) 360 if err != nil { 361 return fmt.Errorf("could not write dataflow to dot-file %q: %w", svc.dotfile, err) 362 } 363 364 return err 365 } 366 367 func newDataFlowSvc(typ, name string, mgr App) (Component, error) { 368 var err error 369 svc := &dflowsvc{ 370 SvcBase: NewSvc(typ, name, mgr), 371 nodes: make(map[string]*node), 372 edges: make(map[string]reflect.Type), 373 dotfile: "", // empty: no dump 374 } 375 376 err = svc.DeclProp("DotFile", &svc.dotfile) 377 if err != nil { 378 return nil, err 379 } 380 381 return svc, err 382 } 383 384 func init() { 385 Register(reflect.TypeOf(dflowsvc{}), newDataFlowSvc) 386 }