1 (* Copyright (C
) 1999-2006 Henry Cejtin
, Matthew Fluet
, Suresh
2 * Jagannathan
, and Stephen Weeks
.
3 * Copyright (C
) 1997-2000 NEC Research Institute
.
5 * MLton is released under a BSD
-style license
.
6 * See the file MLton
-LICENSE for details
.
9 structure Main
: sig val main
: unit
-> unit
end =
17 val callGraphFile
: File
.t option ref
= ref NONE
18 val gray
: bool ref
= ref
false
19 val longName
: bool ref
= ref
true
20 val mlmonFiles
: string list ref
= ref
[]
22 val showLine
= ref
false
23 val splitReg
: Regexp
.t ref
= ref Regexp
.none
24 val title
: string option ref
= ref NONE
25 val tolerant
: bool ref
= ref
false
30 NamePos
of {name
: string,
36 NamePos
{name
, pos
} => concat
[name
, " ", pos
]
39 fun toStringMaybeLine n
=
41 NamePos
{name
, pos
} =>
43 then concat
[name
, " ", pos
]
47 val layout
= Layout
.str
o toString
50 case String.tokens (s
, fn c
=> Char.equals (c
, #
"\t")) of
59 (String.tokens (name
, fn c
=> Char.equals (c
, #
".")))
61 NamePos
{name
= name
, pos
= pos
}
63 | _
=> Error
.bug
"strange source"
67 NamePos
{name
, pos
} =>
69 then [(name
, Dot
.Center
),
71 else [(name
, Dot
.Center
)]
76 structure Graph
= DirectedGraph
86 structure EdgeOption
= EdgeOption
87 structure NodeOption
= NodeOption
92 datatype t
= T
of {callGraph
: unit Graph
.t
,
94 master
: {isSplit
: bool,
95 source
: Source
.t
} vector
,
97 split
: {masterIndex
: int,
98 node
: unit Node
.t
} vector
}
100 fun layout (T
{magic
, name
, master
, ...}) =
102 [("name", String.layout name
),
103 ("magic", Word.layout magic
),
105 Vector.layout (fn {isSplit
, source
} =>
106 Layout
.record
[("isSplit", Bool.layout isSplit
),
107 ("source", Source
.layout source
)])
110 fun new
{afile
: File
.t
}: t
=
113 Error
.bug (concat
["Error: executable '", afile
, "' ", m
, "."])
115 if not (File
.doesExist afile
) then
116 userBug
"does not exist"
117 else if not (File
.canRun afile
) then
118 userBug
"does not run"
121 (OS
.Path
.mkAbsolute
{path
= afile
,
122 relativeTo
= OS
.FileSys
.getDir ()},
123 ["@MLton", "show-sources"],
127 case In
.inputLine ins
of
128 NONE
=> Error
.bug
"unexpected end of show-sources data"
131 case Word.fromString (line ()) of
132 NONE
=> Error
.bug
"expected magic"
134 fun vector (f
: string -> 'a
): 'a vector
=
135 Vector.tabulate (valOf (Int.fromString (line ())),
137 val rc
= Regexp
.compileNFA (!splitReg
)
142 val source
= Source
.fromString (String.dropSuffix (s
, 1))
144 Regexp
.Compiled
.matchesPrefix
145 (rc
, Source
.toString source
)
151 if 0 = Vector.length master
then
152 userBug
"is not compiled for profiling"
157 case String.tokens (s
, Char.isSpace
) of
158 [masterIndex
, successorsIndex
] =>
159 {masterIndex
= valOf (Int.fromString masterIndex
),
160 successorsIndex
= valOf (Int.fromString
162 | _
=> Error
.bug
"AFile.new")
167 (String.tokens (s
, Char.isSpace
), fn s
=>
168 valOf (Int.fromString s
)))
169 val graph
= Graph
.new ()
172 (sources
, fn {masterIndex
, ...} =>
174 val n
= Graph
.newNode graph
176 {masterIndex
= masterIndex
,
182 fn ({successorsIndex
, ...}, {node
= from
, ...}) =>
184 (Vector.sub (sourceSeqs
, successorsIndex
),
186 (ignore
o Graph
.addEdge
)
187 (graph
, {from
= from
,
188 to
= #
node (Vector.sub (split
, to
))})))
190 case In
.inputLine ins
of
192 | SOME _
=> Error
.bug
"expected end of file"
194 T
{callGraph
= graph
,
205 datatype t
= Alloc | Count | Empty | Time
213 val layout
= Layout
.str
o toString
215 val merge
: t
* t
-> t
=
218 (Alloc
, Alloc
) => Alloc
219 |
(Count
, Count
) => Count
222 |
(Time
, Time
) => Time
223 | _
=> Error
.bug
"Kind.merge"
228 datatype t
= Current | Stack
230 (* val toString
= fn Current
=> "Current" | Stack
=> "Stack" *)
232 (* val layout
= Layout
.str
o toString
*)
238 Current
of {master
: IntInf
.t vector
,
239 split
: IntInf
.t vector
}
241 | Stack
of {master
: {current
: IntInf
.t
,
243 stackGC
: IntInf
.t
} vector
,
244 split
: {current
: IntInf
.t
,
246 stackGC
: IntInf
.t
} vector
}
249 fn Current
{master
, split
} =>
250 Layout
.record
[("master", Vector.layout IntInf
.layout master
),
251 ("split", Vector.layout IntInf
.layout split
)]
252 | Empty
=> Layout
.str
"empty"
253 | Stack
{master
, split
} =>
257 (fn {current
, stack
, stackGC
} =>
258 Layout
.record
[("current", IntInf
.layout current
),
259 ("stack", IntInf
.layout stack
),
260 ("stackGC", IntInf
.layout stackGC
)])
263 Layout
.record
[("master", lay master
),
264 ("split", lay split
)]
267 fun merge (c
: t
, c
': t
): t
=
269 (Current
{master
= m
, split
= s
},
270 Current
{master
= m
', split
= s
'}) =>
272 fun merge (v
, v
') = Vector.map2 (v
, v
', op +)
274 Current
{master
= merge (m
, m
'),
275 split
= merge (s
, s
')}
279 |
(Stack
{master
= m
, split
= s
}, Stack
{master
= m
', split
= s
'}) =>
283 (v
, v
', fn ({current
= c
, stack
= s
, stackGC
= g
},
284 {current
= c
', stack
= s
', stackGC
= g
'}) =>
289 Stack
{master
= merge (m
, m
'),
290 split
= merge (s
, s
')}
294 "cannot merge -profile-stack false with -profile-stack true"
299 datatype t
= T
of {counts
: Counts
.t
,
305 fun empty (AFile
.T
{magic
, ...}) =
306 T
{counts
= Counts
.Empty
,
312 fun layout (T
{counts
, kind
, magic
, total
, totalGC
}) =
313 Layout
.record
[("kind", Kind
.layout kind
),
314 ("magic", Word.layout magic
),
315 ("total", IntInf
.layout total
),
316 ("totalGC", IntInf
.layout totalGC
),
317 ("counts", Counts
.layout counts
)]
319 fun new
{mlmonfile
: File
.t
}: t
=
321 (mlmonfile
, fn ins
=>
324 case In
.inputLine ins
of
325 NONE
=> Error
.bug
"unexpected end of mlmon file"
326 | SOME s
=> String.dropSuffix (s
, 1)
328 if "MLton prof" = line ()
330 else Error
.bug
"bad header"
333 "alloc" => Kind
.Alloc
334 |
"count" => Kind
.Count
335 |
"time" => Kind
.Time
336 | _
=> Error
.bug
"invalid profile kind"
339 "current" => Style
.Current
340 |
"stack" => Style
.Stack
341 | _
=> Error
.bug
"invalid profile style"
343 case Word.fromString (line ()) of
344 NONE
=> Error
.bug
"invalid magic"
347 case IntInf
.fromString s
of
348 NONE
=> Error
.bug
"invalid count"
350 val (total
, totalGC
) =
351 case String.tokens (line (), Char.isSpace
) of
352 [total
, totalGC
] => (s2i total
, s2i totalGC
)
353 | _
=> Error
.bug
"invalid totals"
354 fun getCounts (f
: string -> 'a
): {master
: 'a vector
,
358 Vector.tabulate (valOf (Int.fromString (line ())),
360 val split
= vector ()
361 val master
= vector ()
363 {master
= master
, split
= split
}
367 Style
.Current
=> Counts
.Current (getCounts s2i
)
372 case String.tokens (s
, Char.isSpace
) of
379 (concat
["strange line: ",
380 String.dropSuffix (s
, 1)])))
389 fun merge (T
{counts
= c
, kind
= k
, magic
= m
, total
= t
, totalGC
= g
},
390 T
{counts
= c
', kind
= k
', magic
= m
', total
= t
',
393 then Error
.bug
"wrong magic number"
395 T
{counts
= Counts
.merge (c
, c
'),
396 kind
= Kind
.merge (k
, k
'),
405 Name
of string * Regexp
.Compiled
.t
408 | ThreshStack
of real
410 val toSexp
: t
-> Sexp
.t
=
413 datatype z
= datatype Sexp
.t
416 Name (s
, _
) => String s
417 | Thresh x
=> List [Atom
"thresh", Atom (Real.toString x
)]
418 | ThreshGC x
=> List [Atom
"thresh-gc", Atom (Real.toString x
)]
420 List [Atom
"thresh-stack", Atom (Real.toString x
)]
437 val rec toSexp
: t
-> Sexp
.t
=
440 datatype z
= datatype Sexp
.t
441 fun nAry (name
, ps
) =
442 List (Atom name
:: Vector.toListMap (ps
, toSexp
))
443 fun unary (name
, p
) =
444 List [Atom name
, toSexp p
]
447 All
=> Sexp
.Atom
"all"
448 | And ps
=> nAry ("and", ps
)
449 | Atomic a
=> Atomic
.toSexp a
450 | Not p
=> unary ("not", p
)
451 | Or ps
=> nAry ("or", ps
)
452 | PathFrom p
=> unary ("from", p
)
453 | PathTo p
=> unary ("to", p
)
454 | Pred p
=> unary ("pred", p
)
455 | Succ p
=> unary ("succ", p
)
458 (* val layout
= Sexp
.layout
o toSexp
*)
460 val fromString
: string -> t
=
462 case Sexp
.fromString s
of
463 Sexp
.Eof
=> Error
.bug
"empty"
464 | Sexp
.Error s
=> Error
.bug s
467 fun parse (s
: Sexp
.t
): t
=
469 fun err () = Error
.bug (Sexp
.toString s
)
482 f (Vector.fromListMap (ss
, parse
))
490 (case Real.fromString x
of
498 datatype z
= datatype Atomic
.t
504 |
"from" => unary PathFrom
507 |
"pred" => unary Pred
508 |
"succ" => unary Succ
509 |
"thresh" => thresh Thresh
510 |
"thresh-gc" => thresh ThreshGC
513 |
"to" => unary PathTo
518 (case Regexp
.fromString s
of
522 (Atomic
.Name (s
, Regexp
.compileNFA r
)))
528 fun nodes (p
: t
, g
: 'a Graph
.t
,
529 atomic
: 'a Node
.t
* Atomic
.t
-> bool): 'a Node
.t vector
=
531 val {get
= nodeIndex
: 'a Node
.t
-> int,
532 set
= setNodeIndex
, ...} =
533 Property
.getSet (Node
.plist
,
534 Property
.initRaise ("index", Node
.layout
))
535 val nodes
= Vector.fromList (Graph
.nodes g
)
536 val numNodes
= Vector.length nodes
537 val _
= Vector.foreachi (nodes
, fn (i
, n
) => setNodeIndex (n
, i
))
542 val {get
= nodeIndex
': 'a Graph
.u Node
.t
-> int,
543 set
= setNodeIndex
, ...} =
544 Property
.getSet (Node
.plist
,
545 Property
.initRaise ("index", Node
.layout
))
546 val (transpose
, {newNode
, ...}) = Graph
.transpose g
549 (g
, fn n
=> setNodeIndex (newNode n
, nodeIndex n
))
551 (transpose
, newNode
, nodeIndex
')
553 fun vectorToNodes (v
: bool vector
): 'a Node
.t vector
=
557 then SOME (Vector.sub (nodes
, i
))
559 val all
= Promise
.lazy (fn () =>
560 Vector.tabulate (numNodes
, fn _
=> true))
561 val none
= Promise
.lazy (fn () =>
562 Vector.tabulate (numNodes
, fn _
=> false))
563 fun path (v
: bool vector
,
565 getNode
: 'a Node
.t
-> 'b Node
.t
,
566 nodeIndex
: 'b Node
.t
-> int)): bool vector
=
568 val roots
= vectorToNodes v
569 val a
= Array
.array (numNodes
, false)
573 Vector.toListMap (roots
, getNode
),
574 Graph
.DfsParam
.startNode (fn n
=>
576 (a
, nodeIndex n
, true)))
580 fun loop (p
: t
): bool vector
=
584 Vector.fold (ps
, all (), fn (p
, v
) =>
585 Vector.map2 (v
, loop p
, fn (b
, b
') =>
587 | Atomic a
=> Vector.map (nodes
, fn n
=> atomic (n
, a
))
588 | Not p
=> Vector.map (loop p
, not
)
590 Vector.fold (ps
, none (), fn (p
, v
) =>
591 Vector.map2 (v
, loop p
, fn (b
, b
') =>
593 | PathFrom p
=> path (loop p
, (g
, fn n
=> n
, nodeIndex
))
594 | PathTo p
=> path (loop p
, transpose ())
597 val ns
= vectorToNodes (loop p
)
598 val {destroy
, get
, set
, ...} =
599 Property
.destGetSetOnce
600 (Node
.plist
, Property
.initConst
false)
601 val _
= Vector.foreach (ns
, fn n
=> set (n
, true))
606 List.exists (Node
.successors n
, get
o Edge
.to
))
613 val a
= Array
.array (numNodes
, false)
614 fun yes n
= Array
.update (a
, nodeIndex n
, true)
617 (vectorToNodes (loop p
), fn n
=>
619 ; List.foreach (Node
.successors n
, yes
o Edge
.to
)))
629 val keep
: NodePred
.t ref
= ref NodePred
.All
631 val ticksPerSecond
= 100.0
633 fun display (AFile
.T
{callGraph
, master
, name
= aname
, split
, ...},
634 ProfFile
.T
{counts
, kind
, total
, totalGC
, ...}): unit
=
636 val {get
= nodeInfo
: (unit Node
.t
639 mayKeep
: (Atomic
.t
-> bool) ref
}),
640 set
= setNodeInfo
, ...} =
641 Property
.getSetOnce (Node
.plist
,
642 Property
.initRaise ("info", Node
.layout
))
644 Vector.foreachi (split
, fn (i
, {node
, ...}) =>
648 mayKeep
= ref (fn _
=> false)}))
651 Counts
.Current _
=> false
652 | Counts
.Empty
=> false
653 | Counts
.Stack _
=> true
654 val totalReal
= Real.fromIntInf (total
+ totalGC
)
655 val per
: IntInf
.t
-> real =
656 if Real.equals (0.0, totalReal
)
659 fn ticks
=> 100.0 * Real.fromIntInf ticks
/ totalReal
660 fun doit ({master
= masterCount
: 'a vector
,
661 split
= splitCount
: 'a vector
},
662 f
: 'a
-> {current
: IntInf
.t
,
664 stackGC
: IntInf
.t
}) =
668 (split
, fn (i
, {masterIndex
, node
, ...}) =>
670 val {mayKeep
, ...} = nodeInfo node
671 val {isSplit
, source
, ...} = Vector.sub (master
, masterIndex
)
672 val name
= Source
.toString source
677 fun thresh (x
: real, sel
) =
682 else (masterCount
, masterIndex
)
684 per (sel (f (Vector.sub (v
, i
)))) >= x
686 datatype z
= datatype Atomic
.t
690 Regexp
.Compiled
.matchesPrefix (rc
, name
)
691 | Thresh x
=> thresh (x
, #current
)
692 | ThreshGC x
=> thresh (x
, #stackGC
)
693 | ThreshStack x
=> thresh (x
, #stack
)
696 fun row (ticks
: IntInf
.t
): string list
=
697 (concat
[Real.format (per ticks
, Real.Format
.fix (SOME
1)), "%"])
703 ["(", IntInf
.toCommaString ticks
, ")"]
705 ["(", IntInf
.toCommaString ticks
, ")"]
710 (Real.fromIntInf ticks
/ ticksPerSecond
,
711 Real.Format
.fix (SOME
2)),
714 fun info (source
: Source
.t
, a
: 'a
) =
716 val {current
, stack
, stackGC
} = f a
720 then row stack @ row stackGC
723 val isNonZero
= current
> 0 orelse stack
> 0 orelse stackGC
> 0
725 if isNonZero
orelse (kind
= Kind
.Count
726 andalso (case source
of
727 Source
.NamePos _
=> true
730 row
= Source
.toStringMaybeLine source
:: row
}
733 [Dot
.NodeOption
.Shape Dot
.Box
,
735 (Source
.toDotLabel source
737 then [(concat (List.separate (row
, " ")),
742 then DotColor
.gray (100 - Real.round (per stack
))
743 else DotColor
.Black
)]
745 {nodeOptions
= nodeOptions
,
746 tableInfo
= tableInfo
}
750 (master
, masterCount
, fn ({source
, ...}, a
) =>
754 (split
, splitCount
, fn ({masterIndex
, ...}, a
) =>
755 info (#
source (Vector.sub (master
, masterIndex
)), a
))
757 (masterOptions
, splitOptions
)
759 val (masterInfo
, splitInfo
) =
762 doit (ms
, fn z
=> {current
= z
,
766 doit ({master
= Vector.new (Vector.length master
, ()),
767 split
= Vector.new (Vector.length split
, ())},
768 fn () => {current
= 0,
776 (keep
, callGraph
, fn (n
, a
) => (! (#
mayKeep (nodeInfo n
))) a
)
777 val _
= Vector.foreach (keepNodes
, fn n
=>
778 #
keep (nodeInfo n
) := true)
779 (* keep a master node
if it is not split
and some copy
of it is kept
. *)
780 val keepMaster
= Array
.new (Vector.length master
, false)
783 (split
, fn {masterIndex
, node
, ...} =>
785 val {keep
, ...} = nodeInfo node
786 val {isSplit
, ...} = Vector.sub (master
, masterIndex
)
788 if !keep
andalso not isSplit
789 then Array
.update (keepMaster
, masterIndex
, true)
793 val keepGraph
: keep Graph
.t
= Graph
.new ()
794 val {get
= nodeOptions
: keep Node
.t
-> NodeOption
.t list
,
795 set
= setNodeOptions
, ...} =
796 Property
.getSetOnce (Node
.plist
,
797 Property
.initRaise ("options", Node
.layout
))
798 val tableInfos
= ref
[]
799 fun newNode
{nodeOptions
: NodeOption
.t list
,
802 val _
= Option
.app (tableInfo
, fn z
=> List.push (tableInfos
, z
))
803 val n
= Graph
.newNode keepGraph
804 val _
= setNodeOptions (n
, nodeOptions
)
810 (Vector.length master
, fn i
=>
811 if Array
.sub (keepMaster
, i
)
812 then SOME (newNode (Vector.sub (masterInfo
, i
)))
816 (split
, fn (i
, {masterIndex
, node
, ...}) =>
818 val {keep
, ...} = nodeInfo node
819 val {isSplit
, ...} = Vector.sub (master
, masterIndex
)
824 then SOME (newNode (Vector.sub (splitInfo
, i
)))
826 else Vector.sub (masterNodes
, masterIndex
)
830 (callGraph
, fn (from
, e
) =>
833 fun f n
= Vector.sub (splitNodes
, #
index (nodeInfo n
))
835 case (f from
, f to
) of
836 (SOME from
, SOME to
) =>
837 (ignore
o Graph
.addEdge
)
838 (keepGraph
, {from
= from
, to
= to
})
841 val {get
= edgeOptions
: keep Edge
.t
-> EdgeOption
.t list ref
, ...} =
842 Property
.get (Edge
.plist
, Property
.initFun (fn _
=> ref
[]))
843 (* Add a dashed edge from A to B
if there is path from A to B
of length
844 * >= 2 going through only ignored nodes
.
846 fun newNode (n
: unit Node
.t
): keep Node
.t option
=
847 Vector.sub (splitNodes
, #
index (nodeInfo n
))
848 fun reach (root
: unit Node
.t
, f
: keep Node
.t
-> unit
): unit
=
850 val {get
= isKept
: keep Node
.t
-> bool ref
, ...} =
851 Property
.get (Node
.plist
, Property
.initFun (fn _
=> ref
false))
852 val {get
= isSeen
: unit Node
.t
-> bool ref
, ...} =
853 Property
.get (Node
.plist
, Property
.initFun (fn _
=> ref
false))
856 (Node
.successors n
, fn e
=>
875 else (r
:= true; f keepN
)
880 List.foreach (Node
.successors root
, fn e
=>
884 if Option
.isNone (newNode n
)
893 (split
, splitNodes
, fn ({node
= from
, ...}, z
) =>
896 (reach (from
, fn to
=>
898 val e
= Graph
.addEdge (keepGraph
, {from
= from
', to
= to
})
899 val _
= List.push (edgeOptions e
,
900 EdgeOption
.Style Dot
.Dashed
)
904 val _
= Graph
.removeDuplicateEdges keepGraph
907 NONE
=> concat
[aname
, " call-stack graph"]
911 (!callGraphFile
, fn f
=>
915 (Graph
.layoutDot (keepGraph
,
916 fn _
=> {edgeOptions
= ! o edgeOptions
,
917 nodeOptions
= nodeOptions
,
921 (* Display the table
. *)
924 (Vector.fromList (!tableInfos
), fn (z
, z
') => #per z
>= #per z
')
930 [IntInf
.toCommaString total
, " bytes allocated (",
931 IntInf
.toCommaString totalGC
, " bytes by GC)\n"]
933 [IntInf
.toCommaString total
, " ticks\n"]
938 Real.format (Real.fromIntInf i
/ ticksPerSecond
,
939 Real.Format
.fix (SOME
2))
941 [t2s total
, " seconds of CPU time (",
942 t2s totalGC
, " seconds GC)\n"]
949 then ["cur", "stack", "GC"]
953 then List.concatMap (pers
, fn p
=> [p
, "raw"])
957 (if profileStack
then 3 else 1) * (if !raw
then 2 else 1)
963 (table
{columnHeads
= SOME columnHeads
,
964 justs
= Left
:: List.duplicate (cols
, fn () => Right
),
965 rows
= Vector.toListMap (tableRows
, #row
)},
972 fun makeOptions
{usage
} =
977 ([(Normal
, "call-graph", " <file>", "write call graph to dot file",
978 SpaceString (fn s
=> callGraphFile
:= SOME s
)),
979 (Normal
, "graph-title", " <string>", "set call-graph title",
980 SpaceString (fn s
=> title
:= SOME s
)),
981 (Normal
, "gray", " {false|true}", "gray nodes according to stack %",
983 (Normal
, "keep", " <exp>", "which functions to display",
985 keep
:= NodePred
.fromString s
986 handle e
=> usage (concat
["invalid -keep arg: ",
988 (Expert
, "long-name", " {true|false}",
989 " show long names of functions",
991 (Normal
, "mlmon", " <file>", "process mlmon files listed in <file>",
994 List.concat
[String.tokens (File
.contents s
, Char.isSpace
),
996 (Normal
, "raw", " {false|true}", "show raw counts",
998 (Normal
, "show-line", " {false|true}", "show line numbers",
1000 (Normal
, "split", " <regexp>", "split matching functions",
1001 SpaceString (fn s
=>
1002 case Regexp
.fromString s
of
1003 NONE
=> usage (concat
["invalid -split regexp: ", s
])
1004 |
SOME (r
, _
) => splitReg
:= Regexp
.or
[r
, !splitReg
])),
1005 (Normal
, "thresh", " [0.0,100.0]", "-keep (thresh x)",
1006 Real (fn x
=> if x
< 0.0 orelse x
> 100.0
1007 then usage
"invalid -thresh"
1008 else keep
:= NodePred
.Atomic (Atomic
.Thresh x
))),
1009 (Normal
, "tolerant", " {false|true}", "ignore broken mlmon files",
1011 fn (style
, name
, arg
, desc
, opt
) =>
1012 {arg
= arg
, desc
= desc
, name
= name
, opt
= opt
, style
= style
})
1015 val mainUsage
= "mlprof [option ...] a.out [mlmon.out ...]"
1016 val {parse
, usage
} =
1017 Popt
.makeUsage
{mainUsage
= mainUsage
,
1018 makeOptions
= makeOptions
,
1019 showExpert
= fn () => false}
1021 val die
= Process
.fail
1023 fun commandLine args
=
1025 val rest
= parse args
1028 Result
.No msg
=> usage msg
1029 | Result
.Yes (afile
:: files
) =>
1031 val mlmonFiles
= files @
!mlmonFiles
1032 val aInfo
= AFile
.new
{afile
= afile
}
1037 ; Layout
.outputl (AFile
.layout aInfo
, Out
.standard
))
1041 (mlmonFiles
, ProfFile
.empty aInfo
,
1042 fn (mlmonfile
, profFile
) =>
1044 (profFile
, ProfFile
.new
{mlmonfile
= mlmonfile
})
1048 concat
["Error loading mlmon file '", mlmonfile
,
1049 "': ", Exn
.toString e
]
1053 (Out
.outputl (Out
.error
, msg
)
1060 (print
"ProfFile:\n"
1061 ; Layout
.outputl (ProfFile
.layout profFile
,
1064 val _
= display (aInfo
, profFile
)
1068 | Result
.Yes _
=> usage
"wrong number of args"
1071 val main
= Process
.makeMain commandLine