DIRECTORY Atom, Basics, CardTab, Commander, CommanderOps, Imager, IO, PFS, Real, Rope, SF, GGFileOut, GGScene, GGFont, GGSlice, GGModelTypes, GGCoreTypes, GGProps, GGSegment, GGSegmentTypes, NodeProps, GGTraj, ColorFns, NamedColors, ColorTypes, ImagerTransformation, ImagerColor, ThreadsVisPrivate, Feedback, SimpleFeedback, List, GGSliceOps, Convert, BasicTime, SymTab, Process, GCCallBack, EBTypes, EmbeddedButtons, Xl, XlCutBuffers, XTk, XTkWidgets, XTkXBiScroller; ThreadsVisImpl: CEDAR MONITOR IMPORTS Basics, CardTab, CommanderOps, Commander, GGFileOut, ImagerTransformation, IO, GGScene, GGSlice, NodeProps, GGProps, GGSegment, GGTraj, ImagerColor, PFS, ColorFns, NamedColors, ThreadsVisPrivate, GGFont, Feedback, SimpleFeedback, List, Convert, Rope, BasicTime, SymTab, Process, GCCallBack, EmbeddedButtons, XlCutBuffers, XTkWidgets, XTk, XTkXBiScroller = BEGIN ROPE: TYPE = Rope.ROPE; sleepColor: Imager.Color = ImagerColorFromName["blue"]; faintSleepColor: Imager.Color = ImagerColorFromName["very very light vivid blue"]; uncertainSleepColor: Imager.Color = ImagerColorFromName["light blue"]; wakeColor: Imager.Color = ImagerColorFromName["red"]; faintWakeColor: Imager.Color = ImagerColorFromName["very very light vivid red"]; unknownColor: Imager.Color = ImagerColorFromName["light orange"]; notifyTailColor: Imager.Color = ImagerColorFromName["vivid red"]; broadcastTailColor: Imager.Color = ImagerColorFromName["dark vivid red"]; nakedNotifyTailColor: Imager.Color = ImagerColorFromName["dark vivid cyan"]; monitorExitTailColor: Imager.Color = ImagerColorFromName["vivid purple"]; forkTailColor: Imager.Color = ImagerColorFromName["vivid green"]; killTailColor: Imager.Color = ImagerColorFromName["vivid cyan"]; unixIntrColor: Imager.Color = ImagerColorFromName["vivid green"]; unixFaultColor: Imager.Color = ImagerColorFromName["vivid red"]; runSampleColor: Imager.Color = ImagerColorFromName["vivid yellow"]; xSampleColor: Imager.Color = ImagerColorFromName["vivid purple"]; runxColor: Imager.Color = ImagerColorFromName["light vivid purple"]; runyColor: Imager.Color = ImagerColorFromName["light vivid green"]; runzColor: Imager.Color = ImagerColorFromName["light vivid yellow"]; commentTextColor: Imager.Color = ImagerColorFromName["black"]; blackColor: Imager.Color = ImagerColorFromName["black"]; xTickColor: Imager.Color = ImagerColorFromName["very light gray"]; sleepSize: CARD = 4; uncertainSleepSize: CARD =6; wakeSize: CARD = 10; runSize: CARD = 14; unknownSize: CARD = 1; connectSize: REAL = 1.0; arrowTailSize: CARD = 3; commentTextSize: REAL = 12.0; yThreadIncrement: CARD = 14; drawArrowHeads: BOOL = FALSE; Alias: TYPE ~ REF AliasRep; AliasRep: TYPE ~ RECORD [name, alias: ROPE]; ShiftX: TYPE ~ REF ShiftXRep; ShiftXRep: TYPE ~ RECORD [pos: REAL, cnt: CARD]; ButtonInfo: TYPE ~ REF ButtonInfoRep; ButtonInfoRep: TYPE ~ RECORD [msg: ROPE, e, we: ThreadsVisPrivate.Event, t: ThreadsVisPrivate.Thread]; collecting: BOOL _ FALSE; globalOutStream: IO.STREAM _ NIL; globalThreadFacts: ThreadsVisPrivate.ThreadFacts _ NIL; globalEventFacts: ThreadsVisPrivate.EventFacts _ NIL; currentFileName: ROPE; xTransform: CARD ฌ 10; xShoulders: CARD ฌ MAX[(xTransform/2)-1, 1]; xPosTable: CardTab.Ref _ CardTab.Create[]; xPosCountTable: CardTab.Ref _ CardTab.Create[]; maximumArrowThickness: REAL _ arrowTailSize - 1.0; aliasList: LIST OF REF ANY _ NIL; yMax: CARD _ 0; backgroundQueue: ThreadsVisPrivate.Queue _ NIL; backgroundName: ROPE _ NIL; StackPrintCommand: Commander.CommandProc = { stackIdRope: ROPE _ CommanderOps.NextArgument[cmd]; stackId: CARD _ Convert.CardFromRope[stackIdRope ! Convert.Error => CommanderOps.Failed[IO.PutFR1["'%g' is not a valid argument.", IO.rope[stackIdRope]]]]; found: BOOL; cardTabStackList: CardTab.Val; stackList: LIST OF REF ANY; IF globalThreadFacts = NIL THEN CommanderOps.Failed["no data to analyze yet."]; [found, cardTabStackList] _ CardTab.Fetch[NARROW[globalThreadFacts.stackTable], stackId]; IF NOT found THEN CommanderOps.Failed[IO.PutFR1["id %g is not on any stack.", IO.card[stackId]]]; stackList _ NARROW[cardTabStackList]; IO.Put1[globalOutStream, IO.rope[GetStackRope[stackList]]]; }; ThreadsVisCommand: Commander.CommandProc = { threadCount: CARD _ 0; CountThreads: PROC [ThreadsVisPrivate.Thread, LIST OF ThreadsVisPrivate.Thread] ~ { threadCount _ threadCount + 1; }; stackList: LIST OF REF ANY; fileName: ROPE ~ CommanderOps.NextArgument[cmd]; outputFileName: ROPE; globalTransform: ImagerTransformation.Transformation; outStream: IO.STREAM _ cmd.out; sliceQueue: ThreadsVisPrivate.Queue _ NewQueue[]; s: IO.STREAM; scene: GGScene.Scene; tlist: LIST OF ThreadsVisPrivate.Thread; eventList: LIST OF ThreadsVisPrivate.Event; efacts: ThreadsVisPrivate.EventFacts; tfacts: ThreadsVisPrivate.ThreadFacts; commentText: ROPE; readStart, readEnd, buildEnd: BasicTime.GMT; tickProc: PROCESS _ NIL; nextItem: ROPE; IF fileName = NIL THEN CommanderOps.Failed[cmd.procData.doc] ELSE { ENABLE UNWIND => IF tickProc # NIL THEN { KillTicker[tickProc]; }; globalOutStream _ outStream; readStart _ BasicTime.Now[]; GCCallBack.RegisterBefore[NoticeCollection]; IO.PutF1[outStream, "%g", IO.rope["Reading and internalizing"]]; tickProc _ FORK StartTicker[outStream]; [eventFacts: efacts, threadFacts: tfacts, commentText: commentText] _ ThreadsVisPrivate.ReadTreeFromFile[fileName, FALSE]; readEnd _ BasicTime.Now[]; KillTicker[tickProc]; IO.PutF1[outStream, "%g secs. Building scene ", IO.card[BasicTime.Period[from: readStart, to: readEnd]]]; currentFileName _ fileName; outputFileName _ Rope.Concat[fileName, ".gg"]; tickProc _ FORK StartTicker[outStream]; DO nextItem _ CommanderOps.NextArgument[cmd]; IF nextItem = NIL THEN EXIT; SELECT TRUE FROM Rope.Equal[nextItem, "threadList:", FALSE] => { threadList: ROPE _ CommanderOps.NextArgument[cmd]; selected: ROPE _ SelectThreads[threadList, tfacts]; out: ROPE; IF Rope.Length[selected] = 0 THEN out _ "threadList matched no threads: continuing with all." ELSE out _ IO.PutFR1["using only `%g' ", IO.rope[selected]]; IO.Put1[outStream, IO.rope[out]]; commentText _ Rope.Cat[out, "\n", commentText]; }; Rope.Equal[nextItem, "doCedar:", FALSE] => { selected: ROPE _ SelectThreads["*Package* T*.G* VP* IOP* *xnews* *unixkernel* *unixidle*", tfacts]; out: ROPE; IF Rope.Length[selected] = 0 THEN out _ "doCedar matched no threads: continuing with all." ELSE out _ IO.PutFR1["doCedar selected only `%g' ", IO.rope[selected]]; IO.Put1[outStream, IO.rope[out]]; commentText _ Rope.Cat[out, "\n", commentText]; }; Rope.Equal[nextItem, "timeRange:", FALSE] => { out: ROPE; ScaleAllEvents[efacts]; -- want times to be relative to start time of events known so far efacts.min _ Convert.CardFromRope[CommanderOps.NextArgument[cmd] ! Convert.Error => GOTO badRange]; efacts.max _ Convert.CardFromRope[CommanderOps.NextArgument[cmd] ! Convert.Error => GOTO badRange]; out _ IO.PutFR["restricting to %g-%g ", IO.card[efacts.min], IO.card[efacts.max]]; IO.Put1[outStream, IO.rope[out]]; commentText _ Rope.Cat[out, "\n", commentText]; EXITS badRange => CommanderOps.Failed["Bad values following 'timeRange:' option."]; }; Rope.Equal[nextItem, "backgroundThread:", FALSE] => { backgroundName _ CommanderOps.NextArgument[cmd]; commentText _ Rope.Concat[IO.PutFR1[".background thread of '%g'.\n", IO.rope[backgroundName]], commentText]; IO.PutF1[outStream, "using '%g' for background ", IO.rope[backgroundName]]; }; Rope.Equal[nextItem, "moreInput:", FALSE] => { moreFileName: ROPE ฌ CommanderOps.NextArgument[cmd]; efs: ThreadsVisPrivate.EventFacts; tfs: ThreadsVisPrivate.ThreadFacts; ct: ROPE; GCCallBack.RegisterBefore[NoticeCollection]; IO.PutF1[outStream, "%g", IO.rope["Reading and internalizing"]]; [eventFacts: efs, threadFacts: tfs, commentText: ct] _ ThreadsVisPrivate.ReadTreeFromFile[moreFileName, FALSE]; readEnd _ BasicTime.Now[]; IO.PutF1[outStream, "%g secs. ", IO.card[BasicTime.Period[from: readStart, to: readEnd]]]; MergeEvents[into: efacts, from: efs]; MergeThreads[into: tfacts, from: tfs]; }; Rope.Equal[nextItem, "xTransform:", FALSE] => { out: ROPE; xTransform ฌ MAX[Convert.CardFromRope[CommanderOps.NextArgument[cmd] ! Convert.Error => GOTO badTransform], 3]; xShoulders ฌ MAX[(xTransform/2)-1, 1]; out _ IO.PutFR1["xTransform %g ", IO.card[xTransform]]; IO.Put1[outStream, IO.rope[out]]; commentText _ Rope.Cat[out, "\n", commentText]; EXITS badTransform => CommanderOps.Failed["Bad value following 'xTransform:' option."]; }; ENDCASE => CommanderOps.Failed[IO.PutFR1["Unrecognized command option '%g'.", IO.rope[nextItem]]]; ENDLOOP; ScaleAllEvents[efacts]; -- facts are now 1 based; globalThreadFacts _ tfacts; globalEventFacts _ efacts; efacts.min _ efacts.min*xTransform; efacts.max _ efacts.max*xTransform; threadCount _ 0; TMap[tfacts.tlist, CountThreads]; yMax _ threadCount*yThreadIncrement; IF InitAliases[] THEN { IO.Put1[outStream, IO.rope["using ThreadsVis.aliases"]]; ConstructAliases[tfacts]; }; DrawXTicks[sliceQueue, efacts, 1]; DrawThreads[sliceQueue, tfacts, efacts]; DrawEvents[sliceQueue, tfacts, efacts, $S]; DrawEvents[sliceQueue, tfacts, efacts, $W]; DrawEvents[sliceQueue, tfacts, efacts, $R]; DrawEvents[sliceQueue, tfacts, efacts, $Other]; DrawXTicks[sliceQueue, efacts, 1]; commentText _ Rope.Cat[fileName, "\n", IO.PutFR["min time %g ticks, max time %g ticks\n", IO.card[efacts.min/xTransform], IO.card[efacts.max/xTransform]], commentText]; AddText[sliceQueue, efacts.min + (efacts.max-efacts.min)/2, -10-2*commentTextSize, commentText]; LabelThreads[sliceQueue, tfacts, efacts]; scene _ GGScene.CreateScene[]; FOR l: ThreadsVisPrivate.PicLevel IN [background..foreground] DO GGScene.AddSlices[scene, sliceQueue[l]]; ENDLOOP; buildEnd _ BasicTime.Now[]; s _ PFS.StreamOpen[PFS.PathFromRope[outputFileName], create]; KillTicker[tickProc]; IO.PutF[outStream, "%g secs. Writing file '%g'.", IO.card[BasicTime.Period[from: readEnd, to: buildEnd]], IO.rope[outputFileName]]; tickProc _ FORK StartTicker[outStream]; GGFileOut.FileoutSceneOnly[s, scene, outputFileName]; IO.Close[s]; KillTicker[tickProc]; IO.PutF1[outStream, " %g secs.\n", IO.card[BasicTime.Period[from: buildEnd, to: BasicTime.Now[]]]]; }; }; MergeEvents: PROC [into, from: ThreadsVisPrivate.EventFacts] ~ { elLast: LIST OF ThreadsVisPrivate.Event; into.max ฌ MIN[into.max, from.max]; into.min ฌ MAX[into.min, from.min]; FOR el: LIST OF ThreadsVisPrivate.Event ฌ from.elist, el.rest WHILE el#NIL DO [] _ CardTab.Store[NARROW[into.eventTable], el.first.id, el.first]; IF el.rest = NIL THEN elLast ฌ el; ENDLOOP; elLast.rest ฌ into.elist; into.elist ฌ from.elist; }; MergeThreads: PROC [into, from: ThreadsVisPrivate.ThreadFacts] ~ { EachPair: CardTab.EachPairAction ~ { [] ฌ CardTab.Store[NARROW[into.stackTable], key, val]; }; tlLast: LIST OF ThreadsVisPrivate.Thread; FOR tl: LIST OF ThreadsVisPrivate.Thread ฌ from.tlist, tl.rest WHILE tl#NIL DO IF tl.rest=NIL THEN tlLast ฌ tl; ENDLOOP; tlLast.rest ฌ into.tlist; into.tlist ฌ from.tlist; [] ฌ CardTab.Pairs[NARROW[from.stackTable], EachPair]; }; DrawThreads: PROC [sliceQueue: ThreadsVisPrivate.Queue, tfacts: ThreadsVisPrivate.ThreadFacts, efacts: ThreadsVisPrivate.EventFacts] ~ { y: CARD _ 0; drawBackground: BOOL _ FALSE; prevX: CARD _ 0; butInfo: ButtonInfo _ NEW[ButtonInfoRep]; AddCount: PROC [pos: CARD] ~ { found: BOOL; val: CardTab.Val; myVal: REF CARD; [found, val] _ CardTab.Fetch[xPosCountTable, pos]; IF found THEN { myVal _ NARROW[val]; myVal^ _ myVal^ + 1; } ELSE { myVal _ NEW[CARD _ 1]; }; [] _ CardTab.Store[xPosCountTable, pos, myVal]; }; DoEvent: PROC [e: ThreadsVisPrivate.Event, r: LIST OF ThreadsVisPrivate.Event] ~ { start: CARD _ e.time*xTransform; finish: CARD _ e.wakeTime*xTransform; name: ROPE _ Convert.RopeFromAtom[e.type, FALSE]; t: ATOM; butInfo.e _ e; butInfo.we _ NIL; e.ypos _ y; IF (start < efacts.min AND e.time # 0) OR (start > efacts.max) OR (finish > efacts.max) OR (finish # 0 AND finish < efacts.min) THEN RETURN; SELECT TRUE FROM Rope.Match["*sleep*", name, FALSE] => t _ $UnixRun; Rope.Match["*intr*", name, FALSE] OR Rope.Match["*syscall*", name, FALSE] OR Rope.Match["*trap*", name, FALSE] OR Rope.Match["*Fault*", name, FALSE] => t _ $UnixIntr; Rope.Match["*SWTCH*", name, FALSE] => t _ $UnixIgnore; ENDCASE => t _ e.type; SELECT t FROM $UnixRun => { e.xstartpos _ finish; butInfo.msg _ GetEventRope[e]; IF prevX # efacts.min AND prevX < finish THEN { AddToLevel[sliceQueue, ready, MakeLine[prevX, y, finish, y, sleepColor, sleepSize, butInfo]]; IF drawBackground THEN AddToBack[sliceQueue, MakeLine[prevX, yMax/2, start, yMax/2, faintSleepColor, yMax, butInfo, FALSE, butt]]; }; IF prevX = efacts.min AND prevX < start THEN AddToLevel[sliceQueue, unknownThread, MakeLine[prevX, y, finish, y, unknownColor, unknownSize, butInfo]]; e.xendpos _ finish; AddToLevel[sliceQueue, wait, MakeLine[finish-xShoulders, y, start+xShoulders, y, wakeColor, wakeSize, butInfo]]; AddToLevel[sliceQueue, ready, MakeLine[start-xShoulders, y, start+xShoulders, y, sleepColor, sleepSize, butInfo]]; prevX _ start; }; $S => { e.xstartpos _ start; butInfo.msg _ GetEventRope[e]; IF finish = 0 THEN { finish _ start; AddToLevel[sliceQueue, uncertainSleep, MakeLine[start, y, finish, y, uncertainSleepColor, uncertainSleepSize, butInfo]]; }; IF prevX # efacts.min AND prevX < start THEN { AddToLevel[sliceQueue, ready, MakeLine[prevX, y, start, y, wakeColor, wakeSize, butInfo]]; IF drawBackground THEN AddToBack[sliceQueue, MakeLine[prevX, yMax/2, start, yMax/2, faintWakeColor, yMax, butInfo, FALSE, butt]]; }; IF prevX = efacts.min AND prevX < start THEN AddToLevel[sliceQueue, unknownThread, MakeLine[prevX, y, start, y, unknownColor, unknownSize, butInfo]]; e.xendpos _ finish; AddToLevel[sliceQueue, ready, MakeLine[finish-xShoulders, y, finish+xShoulders, y, wakeColor, wakeSize, butInfo]]; AddToLevel[sliceQueue, wait, MakeLine[start-xShoulders, y, finish+xShoulders, y, sleepColor, sleepSize, butInfo]]; prevX _ finish; }; $X, $Y, $Z => { color: Imager.Color ฌ SELECT e.type FROM $X => runxColor, $Y => runyColor, $Z => runzColor, ENDCASE => ERROR; e.xstartpos _ start; butInfo.msg _ GetEventRope[e]; IF finish = 0 THEN finish ฌ start; e.xendpos _ finish; AddToLevel[sliceQueue, execute, MakeLine[start, y, finish, y, color, runSize, butInfo, FALSE, butt]]; AddCount[e.time*xTransform]; }; $W, $R, $UnixIntr => { e.xstartpos _ start; AddCount[e.time*xTransform]; }; $UnixIgnore => { }; ENDCASE => { IO.PutF1[globalOutStream, "unrecognized duration type '%g'\n", IO.atom[e.type]]; }; }; DoThread: PROC [t: ThreadsVisPrivate.Thread, r: LIST OF ThreadsVisPrivate.Thread] ~ { prevX _ efacts.min; y _ y + yThreadIncrement; butInfo.t _ t; IF Rope.Match[backgroundName, t.name, FALSE] THEN drawBackground _ TRUE ELSE drawBackground _ FALSE; EMap[t.elist, DoEvent]; }; TMap[tfacts.tlist, DoThread]; }; DrawEvents: PROC [sliceQueue: ThreadsVisPrivate.Queue, tfacts: ThreadsVisPrivate.ThreadFacts, efacts: ThreadsVisPrivate.EventFacts, type: ATOM] ~ { tmpEvent: CardTab.Val; butInfo: ButtonInfo _ NEW[ButtonInfoRep]; found: BOOL; popupMsg: ROPE _ NIL; missingEvents: CARD _ 0; PaintArrow: PROC [we: ThreadsVisPrivate.Event, xstart, wypos, xend, eypos: REAL, fakeEvent: BOOL, evenOdd: CARD, thickness: REAL] ~ { color: ImagerColor.Color; weName: ROPE _ Convert.RopeFromAtom[we.type, FALSE]; SELECT TRUE FROM CheckName[tfacts, we, "ThreadsQueues._XR_Notify"] => color _ notifyTailColor; CheckName[tfacts, we, "ThreadsQueues._XR_Broadcast"] => color _ broadcastTailColor; CheckName[tfacts, we, "ThreadsQueues._XR_MonitorExitOutOfLine"] => color _ monitorExitTailColor; CheckName[tfacts, we, "Threads1._XR_Fork"] OR CheckName[tfacts, we, "Threads1._XR_TryFork"] => color _ forkTailColor; CheckName[tfacts, we, "ThreadsQueues._XR_NakedNotifyInner"] => color _ nakedNotifyTailColor; CheckName[tfacts, we, "kill._kill"] => color _ killTailColor; we.type = $R => color ฌ runSampleColor; we.type = $X => color ฌ xSampleColor; Rope.Match["*intr*", weName, FALSE] => color _ unixIntrColor; Rope.Match["*Fault*", weName, FALSE] => color _ unixFaultColor; CheckName[tfacts, we, "*unixkernel*"] OR CheckName[tfacts, we, "*unixidle*"]=> color _ blackColor; ENDCASE => { color _ blackColor; SimpleFeedback.Append[$ThreadsVis, oneLiner, $Feedback, IO.PutFR1["wakeup event %g of unrecognized type by stack name", IO.card[we.id]]]; }; IF NOT fakeEvent AND color # blackColor THEN { IF Basics.OddCard[evenOdd] THEN wypos _ wypos + 1 ELSE wypos _ wypos - 1; IF eypos > wypos THEN eypos _ eypos - (wakeSize/2) ELSE eypos _ eypos + (wakeSize/2); }; AddToLevel[sliceQueue, event, MakeLine[xstart, wypos, xstart, wypos, color, arrowTailSize, butInfo, FALSE, round]]; AddToLevel[sliceQueue, event, MakeLine[xstart, wypos, xend, eypos, color, thickness, butInfo, drawArrowHeads]] }; DoEvent: PROC [e: ThreadsVisPrivate.Event, r: LIST OF ThreadsVisPrivate.Event] ~ { cnt: CARD; t: ATOM; computedThickness: REAL; name: ROPE _ Convert.RopeFromAtom[e.type, FALSE]; butInfo.e _ e; butInfo.we _ NIL; IF (e.time*xTransform < efacts.min AND e.time # 0) OR (e.wakeTime*xTransform > efacts.max) OR (e.time*xTransform > efacts.max) THEN RETURN; IF e.type = type OR (type = $Other AND Rope.Length[name] > 1) THEN { IF Rope.Match["*sleep*", name, FALSE] OR Rope.Match["*SWTCH*", name, FALSE]THEN t _ $UnixRun ELSE IF Rope.Match["*intr*", name, FALSE] OR Rope.Match["*syscall*", name, FALSE] OR Rope.Match["*trap*", name, FALSE] OR Rope.Match["*Fault*", name, FALSE] THEN t _ $UnixIntr ELSE t _ e.type; SELECT t FROM $UnixRun => { }; $S => { fake: BOOL _ FALSE; IF e.wakeEvent # 0 THEN { [found, tmpEvent] _ CardTab.Fetch[NARROW[efacts.eventTable], e.wakeEvent] ; IF NOT found THEN { SimpleFeedback.Append[$ThreadsVis, oneLiner, $Feedback, IO.PutFR1["ThreadsVis: event id %g not found.", IO.card[e.wakeEvent]]]; } ELSE { wakeEvent: ThreadsVisPrivate.Event _ NARROW[tmpEvent]; fixedxstart: REAL; fixedxend: REAL; butInfo.msg _ IO.PutFR["from: %g to: %g", IO.card[wakeEvent.id], IO.card[e.id]]; butInfo.we _ wakeEvent; IF wakeEvent.xstartpos = 0 THEN { wakeEvent.ypos _ e.ypos+yThreadIncrement/2; wakeEvent.xstartpos _ wakeEvent.xendpos _ e.xendpos; butInfo.msg _ IO.PutFR["from: %g to: %g (thread not shown)", IO.card[wakeEvent.id], IO.card[e.id]]; fake _ TRUE; }; [fixedxstart, cnt, computedThickness] _ GetFixedX[xPosTable, wakeEvent.xstartpos, wakeEvent]; IF wakeEvent.xstartpos = e.xendpos THEN fixedxend _ fixedxstart ELSE [fixedxstart, cnt, computedThickness] _ GetFixedX[xPosTable, e.xendpos, e]; butInfo.msg _ IO.PutFR["from: %g to: %g", IO.card[wakeEvent.id], IO.card[e.id]]; PaintArrow[wakeEvent, fixedxstart, wakeEvent.ypos, fixedxend, e.ypos, fake, cnt, computedThickness]; wakeEvent.drawn _ TRUE; }; }; }; $W, $UnixIntr => { fixedxstart: REAL; IF NOT e.drawn THEN { butInfo.msg _ IO.PutFR1["from: %g to: unknown", IO.rope[GetEventRope[e]]]; [fixedxstart, cnt, computedThickness] _ GetFixedX[xPosTable, e.xstartpos, e]; PaintArrow[e, fixedxstart, e.ypos, fixedxstart, e.ypos+yThreadIncrement/2, TRUE, cnt, computedThickness]; e.drawn _ TRUE; }; }; $X, $Y, $Z => { }; $R => { fixedxstart: REAL; IF NOT e.drawn THEN { butInfo.msg _ IO.PutFR1["from: %g to: NA", IO.rope[GetEventRope[e]]]; [fixedxstart, cnt, computedThickness] _ GetFixedX[xPosTable, e.xstartpos, e]; PaintArrow[e, fixedxstart, e.ypos, fixedxstart, e.ypos+yThreadIncrement/2, TRUE, cnt, computedThickness]; e.drawn _ TRUE; }; }; ENDCASE => { IO.PutF1[globalOutStream, "unrecognized event type '%g'\n", IO.atom[e.type]]; }; }; }; DoThread: PROC [t: ThreadsVisPrivate.Thread, r: LIST OF ThreadsVisPrivate.Thread] ~ { butInfo.t _ t; EMap[t.elist, DoEvent]; }; TMap[tfacts.tlist, DoThread]; IF missingEvents > 0 THEN SimpleFeedback.Append[$ThreadsVis, oneLiner, $Feedback, IO.PutFR1["ThreadsVis: %g uncaused wakeups.", IO.card[missingEvents]]]; }; CheckName: PROC [tfacts: ThreadsVisPrivate.ThreadFacts, e: ThreadsVisPrivate.Event, name: ROPE] RETURNS [v: BOOL] ~ { nameTable: REF; stack: LIST OF REF ANY; stack _ NARROW[CardTab.Fetch[NARROW[tfacts.stackTable], e.node].val]; v _ SearchStackForName[name, stack]; RETURN [v]; }; SearchStackForName: PROC [pattern: ROPE, stack: LIST OF REF ANY] RETURNS [returnVal: BOOL] ~ { FOR each: LIST OF REF ANY _ stack, each.rest UNTIL each = NIL DO IF Rope.Match[pattern, NARROW[each.first, ThreadsVisPrivate.StackEntry].name] THEN RETURN [TRUE]; ENDLOOP; RETURN [FALSE]; }; GetFixedX: PROC [t: CardTab.Ref, x: CARD, e: ThreadsVisPrivate.Event] RETURNS [pos: REAL, cnt: CARD, thickness: REAL] ~ { found: BOOL; myVal: ShiftX; val, countVal: CardTab.Val; oldCount: CARD; [found, countVal] _ CardTab.Fetch[xPosCountTable, x]; IF NOT found THEN CommanderOps.Failed[IO.PutFR["ThreadsVis: time %g (event %g) not in count table.", IO.card[x], IO.card[e.id]]]; oldCount _ NARROW[countVal, REF CARD]^; thickness _ MAX[(xTransform-3)/(1.5*oldCount), 0.2]; IF thickness > maximumArrowThickness THEN thickness _ maximumArrowThickness; [found, val] _ CardTab.Fetch[t, x]; IF found THEN { myVal _ NARROW[val]; myVal.pos _ myVal.pos + thickness*1.5; myVal.cnt _ myVal.cnt + 1; } ELSE { myVal _ NEW[ShiftXRep _ [x, 0]]; }; [] _ CardTab.Store[t, x, myVal]; RETURN [myVal.pos, myVal.cnt, thickness]; }; MakeLine: PROC [startx, starty, finishx, finishy: REAL, color: Imager.Color, thickness: REAL, msg: ButtonInfo, arrow: BOOL _ FALSE, endType: Imager.StrokeEnd _ butt --round--] RETURNS [return: GGSlice.Slice] = { FixSeg: PROC [seg: GGSlice.Segment, width: REAL] ~ { seg.color _ color; seg.strokeWidth _ width; seg.strokeEnd _ endType; }; slice: GGSlice.Slice _ GGTraj.CreateTraj[[startx, starty]]; seg: GGSlice.Segment _ GGSegment.MakeLine[[startx, starty], [finishx, finishy], NIL]; FixSeg[seg, thickness]; [] _ GGTraj.AddSegment[slice, hi, seg, lo] ; IF arrow THEN { arrowSeg: GGSlice.Segment; sign: INT; IF starty > finishy THEN sign _ 1 ELSE sign _ -1; arrowSeg _ GGSegment.MakeLine[[finishx, finishy], [finishx-1, finishy+(1*sign)], NIL]; FixSeg[arrowSeg, thickness*1.5]; [] _ GGTraj.AddSegment[slice, hi, arrowSeg, lo] ; arrowSeg _ GGSegment.MakeLine[[finishx-1, finishy+(1*sign)], [finishx, finishy], NIL]; FixSeg[arrowSeg, thickness*1.5]; [] _ GGTraj.AddSegment[slice, hi, arrowSeg, lo] ; arrowSeg _ GGSegment.MakeLine[[finishx, finishy], [finishx+1, finishy+(1*sign)], NIL]; FixSeg[arrowSeg, thickness*1.5]; [] _ GGTraj.AddSegment[slice, hi, arrowSeg, lo] ; }; IF msg # NIL THEN AddSliceButton[slice, NIL, msg]; RETURN [slice]; }; AddText: PROC [sliceQueue: ThreadsVisPrivate.Queue, x, y: REAL, text: ROPE, where: ThreadsVisPrivate.FrontBackType _ front] = { router: Feedback.MsgRouter _ Feedback.CreateRouter[]; slice: GGSlice.Slice; fontdata: GGSlice.FontData _ GGFont.CreateFontData[]; bar: BOOLEAN; newlinePos: INT; thisText: ROPE; currentY: REAL _ y; fontdata _ GGFont.InitFontData[fontdata]; fontdata.literal _ "xerox/xc1-2-2/Modern-bold-italic"; fontdata.prefix _ "xerox/xc1-2-2/"; fontdata.literalFSF _ "Modern-BI"; fontdata.userFSF _ "Modern"; UNTIL Rope.Length[text] = 0 DO newlinePos _ Rope.SkipTo[text, 0, "\n"]; thisText _ Rope.Substr[text, 0, newlinePos]; -- don't get the newline IF Rope.Length[text] = newlinePos THEN text _ NIL ELSE text _ Rope.Substr[text, newlinePos+1]; thisText _ ExpandTabs[thisText]; slice _ GGSlice.MakeTextSlice[thisText, commentTextColor, screen]; fontdata.transform _ ImagerTransformation.Scale[commentTextSize]; fontdata.transform _ ImagerTransformation.TranslateTo[fontdata.transform, [x, currentY]]; bar _ GGSlice.SetTextFontAndTransform[slice, fontdata, router, NIL]; SELECT where FROM front => AddToBack[sliceQueue, slice]; back => AddToFront[sliceQueue, slice]; ENDCASE; currentY _ currentY - commentTextSize; ENDLOOP; }; ImagerColorFromName: PROC [name: ROPE] RETURNS [Imager.Color] = { color: Imager.Color _ ImagerColor.ColorFromRGB[ColorFns.RGBFromHSL[NamedColors.RopeToHSL[name]]]; RETURN [color]; }; GetColorName: PROC [color: Imager.Color] RETURNS [r: ROPE] = { IF color = sleepColor THEN RETURN ["sleep"]; IF color = wakeColor THEN RETURN ["wake"]; IF color = unknownColor THEN RETURN ["unknown"]; IF color = uncertainSleepColor THEN RETURN ["uncertain"]; IF color = notifyTailColor THEN RETURN ["notifyTailColor"]; IF color = broadcastTailColor THEN RETURN ["broadcastTailColor"]; IF color = monitorExitTailColor THEN RETURN ["broadcastTailColor"]; IF color = forkTailColor THEN RETURN ["forkTailColor"]; RETURN ["badcolor"]; }; DrawLegend: PROC [sliceQueue: ThreadsVisPrivate.Queue, xFinal, yFinal: REAL] ~ { y: REAL _ yFinal; PutText: PROC [s: ROPE] ~ { AddText[sliceQueue, xFinal+15, y-5, s, back]; y _ y - commentTextSize; }; AddToFront[sliceQueue, MakeLine[xFinal, y, xFinal, y, uncertainSleepColor, uncertainSleepSize, NIL]]; PutText["uncaused event (timeout?)"]; AddToFront[sliceQueue, MakeLine[xFinal, y, xFinal+4, y, wakeColor, wakeSize, NIL]]; PutText["ready thread"]; AddToFront[sliceQueue, MakeLine[xFinal, y, xFinal+4, y, unknownColor, unknownSize, NIL]]; PutText["unknown thread state"]; AddToFront[sliceQueue, MakeLine[xFinal, y, xFinal+4, y, sleepColor, sleepSize, NIL]]; PutText["sleeping thread"]; AddToFront[sliceQueue, MakeLine[xFinal, y-2, xFinal, y-2, notifyTailColor, arrowTailSize, NIL]]; AddToFront[sliceQueue, MakeLine[xFinal, y-2, xFinal, y+6, notifyTailColor, connectSize, NIL, drawArrowHeads]]; PutText["notify"]; AddToFront[sliceQueue, MakeLine[xFinal, y-2, xFinal, y-2, broadcastTailColor, arrowTailSize, NIL]]; AddToFront[sliceQueue, MakeLine[xFinal, y-2, xFinal, y+6, broadcastTailColor, connectSize, NIL, drawArrowHeads]]; PutText["broadcast"]; AddToFront[sliceQueue, MakeLine[xFinal, y-2, xFinal, y-2, monitorExitTailColor, arrowTailSize, NIL]]; AddToFront[sliceQueue, MakeLine[xFinal, y-2, xFinal, y+6, monitorExitTailColor, connectSize, NIL, drawArrowHeads]]; PutText["monitor exit"]; AddToFront[sliceQueue, MakeLine[xFinal, y-2, xFinal, y-2, forkTailColor, arrowTailSize, NIL]]; AddToFront[sliceQueue, MakeLine[xFinal, y-2, xFinal, y+6, forkTailColor, connectSize, NIL, drawArrowHeads]]; PutText["fork"]; AddToFront[sliceQueue, MakeLine[xFinal, y-2, xFinal, y-2, nakedNotifyTailColor, arrowTailSize, NIL]]; AddToFront[sliceQueue, MakeLine[xFinal, y-2, xFinal, y+6, nakedNotifyTailColor, connectSize, NIL, drawArrowHeads]]; PutText["naked notify"]; AddToFront[sliceQueue, MakeLine[xFinal, y-2, xFinal, y-2, killTailColor, arrowTailSize, NIL]]; AddToFront[sliceQueue, MakeLine[xFinal, y-2, xFinal, y+6, killTailColor, connectSize, NIL, drawArrowHeads]]; PutText["unix 'kill' (of IOP)"]; }; LabelThreads: PROC [sliceQueue: ThreadsVisPrivate.Queue, tfacts: ThreadsVisPrivate.ThreadFacts, efacts: ThreadsVisPrivate.EventFacts] ~ { y: INT _ 0; DoThread: PROC [t: ThreadsVisPrivate.Thread, r: LIST OF ThreadsVisPrivate.Thread] ~ { name: ROPE _ GetThreadNameAndAliases[t]; y _ y + yThreadIncrement; AddText[sliceQueue, efacts.max+10, y-3, name, back]; }; TMap[tfacts.tlist, DoThread]; }; GetThreadNameAndAliases: PROC [t: ThreadsVisPrivate.Thread] RETURNS [val: ROPE] ~ { IF t # NIL THEN { val _ Rope.Concat[ThreadsVisPrivate.STName[NARROW[t.tree]], t.alias]; } ELSE { val _ "*noname*"; }; }; DrawXTicks: PROC [sliceQueue: ThreadsVisPrivate.Queue, efacts: ThreadsVisPrivate.EventFacts, frequency: CARD] ~ { buttonData: ButtonInfo; FOR x: CARD _ 0, x+xTransform*frequency UNTIL x > efacts.max DO buttonData _ NEW[ButtonInfoRep _ [IO.PutFR1["time is %d.", IO.card[(x/xTransform)+efacts.min-1]], NIL, NIL]]; AddToBack[sliceQueue, MakeLine[x, 0, x, yMax, xTickColor, 0.1, buttonData ]]; ENDLOOP; }; GetEventRope: PROC [e: ThreadsVisPrivate.Event] RETURNS [out: ROPE] ~ { RETURN [IO.PutFLR["%g %g %g %g %g %g", LIST[ IO.card[e.id], IO.atom[e.type], IO.card[e.time], IO.card[e.node], IO.card[e.wakeTime], IO.card[e.wakeEvent]]]]; }; InitAliases: PROC [] RETURNS [returnVal: BOOL _ FALSE] ~ { ENABLE { IO.EndOfStream => GOTO Done; PFS.Error => {returnVal _ FALSE; GOTO Done}; }; name, alias: ROPE; in: IO.STREAM _ PFS.StreamOpen[PFS.PathFromRope["ThreadsVis.aliases"], read]; DO name _ IO.GetRopeLiteral[in]; alias _ IO.GetRopeLiteral[in]; aliasList _ CONS[NEW[AliasRep _ [name, alias]], aliasList]; returnVal _ TRUE; ENDLOOP; EXITS Done => RETURN; }; ConstructAliases: PROC [tfacts: ThreadsVisPrivate.ThreadFacts] ~ { myThread: ThreadsVisPrivate.Thread; alias: Alias; ForEachName: SymTab.EachPairAction ~ { IF Rope.Match[alias.name, key, FALSE] THEN { myThread.alias _ Rope.Cat[myThread.alias, " - ", alias.alias]; RETURN [TRUE]; }; }; ForEachThread: PROC [t: ThreadsVisPrivate.Thread, r: LIST OF ThreadsVisPrivate.Thread] ~ { myThread _ t; [] _ SymTab.Pairs[NARROW[t.nameTable], ForEachName]; }; ForEachAlias: PROC[item: REF ANY, l: LIST OF REF ANY] ~ { alias _ NARROW[item]; TMap[tfacts.tlist, ForEachThread]; }; IF aliasList # NIL THEN List.Map[aliasList, ForEachAlias]; }; PrintThreadsLists: PROC [s: IO.STREAM, tlist: LIST OF ThreadsVisPrivate.Thread] ~ { DoEvent: PROC [e: ThreadsVisPrivate.Event, r: LIST OF ThreadsVisPrivate.Event] ~ { IO.PutF1[s, "%g\n", IO.rope[GetEventRope[e]]]; }; DoThread: PROC [t: ThreadsVisPrivate.Thread, r: LIST OF ThreadsVisPrivate.Thread] ~ { IO.PutF1[s, "------------ %g\n", IO.rope[t.name]]; EMap[t.elist, DoEvent]; }; TMap[tlist, DoThread]; }; PrintThreadTree: PROC [stream: IO.STREAM, t: LIST OF REF ANY] ~ { DoStack: PROC [s: REF ANY, r: LIST OF REF ANY] ~ { stack: LIST OF REF ANY _ NARROW[s]; IO.PutF1[stream, "%g\n", IO.rope["-------------------"]]; IO.Put1[stream, IO.rope[GetStackRope[stack]]]; }; List.Map[t, DoStack]; }; GetStackRope: PROC [t: LIST OF REF ANY] RETURNS [rope: ROPE] ~ { PrintIt: PROC [s: REF ANY, r: LIST OF REF ANY] ~ { stack: ThreadsVisPrivate.StackEntry _ NARROW[s]; rope _ Rope.Concat[rope, IO.PutFR1["***%g\n", IO.rope[stack.name]]]; }; List.Map[t, PrintIt]; }; PrintThreadEvents: PROC [tlist: LIST OF ThreadsVisPrivate.Thread] = { printProc: CardTab.EachPairAction = { SimpleFeedback.Append[$ThreadsVis, oneLiner, $Feedback, IO.PutFR1["event %g in thread.", IO.card[key]]]; }; t: ThreadsVisPrivate.Thread; t _ tlist.first; WHILE tlist.rest # NIL DO t _ tlist.first; SimpleFeedback.Append[$ThreadsVis, oneLiner, $Feedback, IO.PutFR1["size %g.", IO.card[CardTab.GetSize[NARROW[t.idList]]]]]; [] _ CardTab.Pairs[NARROW[t.idList], printProc]; tlist _ tlist.rest; ENDLOOP; }; DumpSymTab: PROC [tab: REF] ~ { action: SymTab.EachPairAction ~ { IO.PutF1[globalOutStream, "'%g' ", IO.rope[key]]; }; IO.PutF1[globalOutStream, "%g", IO.rope["-------symtab--------: "]]; [] _ SymTab.Pairs[NARROW[tab], action]; IO.PutF1[globalOutStream, "%g", IO.rope["\n"]]; }; ExpandTabs: PROC [in: ROPE, tabStop: CARD _ 8] RETURNS [out: ROPE _ NIL] ~ { lastPos: INT _ 0; skipPos: INT; DO skipPos _ Rope.SkipTo[in, lastPos, "\t"]; IF skipPos = Rope.Length[in] THEN RETURN [Rope.Concat[out,Rope.Substr[in, lastPos]]]; out _ Rope.Concat[out, Rope.Substr[in, lastPos, skipPos-lastPos]]; out _ Rope.Concat[out, Rope.Substr[" ", Rope.Length[out] MOD tabStop]]; lastPos _ skipPos + 1; ENDLOOP; }; ScaleAllEvents: PROC [efacts: ThreadsVisPrivate.EventFacts] ~ { ForEachEvent: PROC [e: ThreadsVisPrivate.Event, r: LIST OF ThreadsVisPrivate.Event] ~ { IF e.time # 0 THEN e.time _ e.time - efacts.min + 1 ELSE e.time ฌ efacts.min + 1; IF e.wakeTime # 0 THEN e.wakeTime _ e.wakeTime - efacts.min + 1; }; EMap[efacts.elist, ForEachEvent]; efacts.max _ efacts.max - efacts.min + 1; efacts.min _ 1; }; ReverseSliceList: PROC [in: LIST OF GGSlice.Slice] RETURNS [out: LIST OF GGSlice.Slice] ~ { out ฌ NIL; UNTIL in = NIL DO out ฌ CONS[in.first, out]; in ฌ in.rest; ENDLOOP; RETURN[out]; }; MapSlices: PROC [in: LIST OF GGSlice.Slice, p: PROC [GGSlice.Slice]] ~ { s: GGSlice.Slice; UNTIL in = NIL DO s _ in.first; p[s]; in _ in.rest; ENDLOOP; }; TMap: PROC [list: LIST OF ThreadsVisPrivate.Thread, proc: PROC[ThreadsVisPrivate.Thread, LIST OF ThreadsVisPrivate.Thread]] ~ { WHILE list # NIL DO proc[list.first, list]; list _ list.rest; ENDLOOP; }; EMap: PROC [list: LIST OF ThreadsVisPrivate.Event, proc: PROC[ThreadsVisPrivate.Event, LIST OF ThreadsVisPrivate.Event]] ~ { WHILE list # NIL DO proc[list.first, list]; list _ list.rest; ENDLOOP; }; NewQueue: PROC [] RETURNS [newQueue: ThreadsVisPrivate.Queue] ~ { newQueue _ NEW[ThreadsVisPrivate.QueueRep]; }; AddToFront: PROC [q: ThreadsVisPrivate.Queue, r: GGSlice.Slice] ~ { q[foreground] ฌ CONS[r, q[foreground]]; }; AddToLevel: PROC [q: ThreadsVisPrivate.Queue, l: ThreadsVisPrivate.PicLevel, r: GGSlice.Slice] ~ { q[l] ฌ CONS[r, q[l]]; }; AddToBack: PROC [q: ThreadsVisPrivate.Queue, r: GGSlice.Slice] ~ { q[background] ฌ CONS[r, q[background]]; }; SelectThreads: PROC [threadListRope: ROPE, tfacts: ThreadsVisPrivate.ThreadFacts] RETURNS [selected: ROPE _ NIL] ~ { newTlist: LIST OF ThreadsVisPrivate.Thread _ NIL; newTail: LIST OF ThreadsVisPrivate.Thread _ NIL; currentName: ROPE; DoPerThread: PROC [t: ThreadsVisPrivate.Thread, r: LIST OF ThreadsVisPrivate.Thread] ~ { IF Rope.Match[currentName, ThreadsVisPrivate.STName[NARROW[t.tree]]] THEN { IF newTail = NIL THEN newTlist ฌ newTail ฌ CONS[t, NIL] ELSE { newTail ฌ newTail.rest ฌ CONS[t, NIL]; }; selected _ Rope.Cat[selected, " ", ThreadsVisPrivate.STName[NARROW[t.tree]]]; }; }; inTok: IO.STREAM _ IO.RIS[threadListRope]; nameList: LIST OF ROPE _ NIL; DO currentName _ IO.GetTokenRope[inTok, IO.IDProc ! IO.EndOfStream => EXIT].token; TMap[tfacts.tlist, DoPerThread]; ENDLOOP; IF newTlist # NIL THEN tfacts.tlist _ newTlist; }; KillTicker: PROC [tickProc: PROCESS] ~ { ENABLE Process.InvalidProcess => CONTINUE; Process.Abort[tickProc]; Process.Detach[tickProc]; }; StartTicker: PROC [s: IO.STREAM] ~ { maxTicks: CARD = 50; ticks: CARD _ 0; collecting _ FALSE; DO Process.CheckForAbort[]; IF collecting THEN { IO.Put1[s, IO.char['g]]; collecting _ FALSE; }; IO.Put1[s, IO.char['.]]; Process.PauseMsec[1000]; ticks _ ticks + 1; IF ticks > maxTicks THEN Process.Abort[Process.GetCurrent[]]; ENDLOOP; }; NoticeCollection: PROC [r: REF] ~ { collecting _ TRUE; }; theButtonDataKey: ATOM ~ $ButtonData; theButtonDataRope: ROPE ~ "Poppy1 Class: PopUpButton MessageHandler: Tioga Menu: ( ((\"%g\") \"%g\" \"Put event id at tioga caret\") (\"\" \"%g\" \"The event id.\") (\"\" \"%g\" \"The thread name.\") ( \"put S stack\" \"Insert stack of sleeping thread to tioga caret.\") ( \"pop S stack\" \"Popup stack of sleeping thread to tioga caret.\") (\"\" \"\" \"\") ( \"put W stack\" \"Insert stack of signalling thread to tioga caret.\") ( \"pop W stack\" \"Popup stack of sleeping thread to tioga caret.\") ) Feedback: ( (MouseMoved ) )"; theButtonDataValue: REF ~ NodeProps.DoSpecs[theButtonDataKey, theButtonDataRope]; TVGetStackRopeHelper: EBTypes.EBLanguageProc = { outRope: ROPE _ NIL; eventId: CARD _ 0; eventCardTab: CardTab.Val; event: ThreadsVisPrivate.Event; found: BOOL; cardTabStackList: CardTab.Val; stackList: LIST OF REF ANY; WITH arguments.first SELECT FROM c: REF CARD => eventId _ c^; i: REF INT => eventId _ i^; ENDCASE; IF globalThreadFacts = NIL THEN RETURN["-----ThreadsVis has no active file."]; [found, eventCardTab] _ CardTab.Fetch[NARROW[globalEventFacts.eventTable], eventId]; IF NOT found THEN RETURN[Rope.Concat[outRope, IO.PutFR1["-----%g is not a known id.", IO.card[eventId]]]]; event _ NARROW[eventCardTab]; outRope _ IO.PutFR1["-----stack for event: %g\n", IO.rope[GetEventRope[event]]]; [found, cardTabStackList] _ CardTab.Fetch[NARROW[globalThreadFacts.stackTable], event.node]; IF NOT found THEN RETURN[Rope.Concat[outRope, IO.PutFR1["-----id %g is not on any stack.", IO.card[event.node]]]]; stackList _ NARROW[cardTabStackList]; outRope _ Rope.Concat[outRope, IO.PutFR1["%g", IO.rope[GetStackRope[stackList]]]]; RETURN[outRope]; }; TVGetStack: EBTypes.EBLanguageProc = { RETURN[TVGetStackRopeHelper[arguments, buttonInfo, clientData, context]]; }; TVPopupXStack: EBTypes.EBLanguageProc = { rope: ROPE _ NARROW[TVGetStackRopeHelper[arguments, buttonInfo, clientData, context]]; PopupText["ThreadsVis Stacks", rope]; }; AddSliceButton: PROC [slice: GGSlice.Slice, parts: GGSlice.SliceParts, buttonInfo: ButtonInfo] ~ { GetNameFromEvent: PROC [we: ThreadsVisPrivate.Event] RETURNS [r: ROPE] ~ { tmpEvent: CardTab.Val; oldName: ROPE _ NIL; newName: ROPE _ NIL; cardTabStackList: CardTab.Val; stackList: LIST OF REF ANY; found: BOOL; IF NOT Rope.Match["*from:*", buttonInfo.msg] THEN RETURN [NIL]; IF buttonInfo.we = NIL THEN RETURN [NIL]; [found, cardTabStackList] _ CardTab.Fetch[NARROW[globalThreadFacts.stackTable], buttonInfo.we.node]; IF NOT found THEN RETURN [NIL]; stackList _ NARROW[cardTabStackList]; FOR stack: LIST OF REF ANY _ stackList, stack.rest UNTIL stack = NIL DO oldName _ newName; newName _ NARROW[stack.first, ThreadsVisPrivate.StackEntry].name; ENDLOOP; RETURN [oldName]; }; MakeFancyName: PROC [a, b: ROPE] RETURNS [r: ROPE] ~ { IF b = NIL THEN RETURN [a] ELSE RETURN [IO.PutFR["from %g to %g", IO.rope[b], IO.rope[a]]]; }; fancyName: ROPE _ MakeFancyName[GetThreadNameAndAliases[buttonInfo.t], GetNameFromEvent[buttonInfo.e]]; r: REF; weId, eId: CARD; IF buttonInfo.we = NIL THEN weId _ 0 ELSE weId _ buttonInfo.we.id; IF buttonInfo.e = NIL THEN eId _ 0 ELSE eId _ buttonInfo.e.id; r _ NodeProps.DoSpecs[theButtonDataKey, IO.PutFLR[theButtonDataRope, LIST[ IO.rope[buttonInfo.msg], IO.rope[buttonInfo.msg], IO.rope[buttonInfo.msg], IO.rope[fancyName], IO.card[eId], IO.card[eId], IO.card[weId], IO.card[weId]]]]; GGProps.Put[slice, parts, theButtonDataKey, r]; }; Widget: TYPE = XTk.Widget; PopupText: PROC [header, text: ROPE] ~ { shell: Widget ~ XTkWidgets.CreateShell[ className: $ThreadsVis, windowHeader: header, packageName: "ThreadsVis", shortName: "ThreadsVis" ]; doneButton, refreshButton, stuffXButton, streamWidget, scroller, innerContainer, container, menu: Widget; stream: IO.STREAM; streamWidget _ XTkWidgets.CreateStreamWidget[widgetSpec: [geometry: [size: [-1, 5000]]]]; stream _ XTkWidgets.CreateStream[streamWidget]; doneButton _ XTkWidgets.CreateButton[[], " Done ", [], XPopupDoneProc]; refreshButton _ XTkWidgets.CreateButton[[], " Refresh ", [], XPopupRefreshProc, stream, text]; stuffXButton _ XTkWidgets.CreateButton[[], " Stuff X ", [], XPopupStuffXProc, text]; menu _ XTkWidgets.CreateXStack[stack: LIST[doneButton, refreshButton, stuffXButton]]; scroller _ XTkXBiScroller.CreateXBiScroller[widgetSpec: [geometry: [size: [-1, 500]]], child: streamWidget, hsbar: FALSE]; container _ XTkWidgets.CreateYStack[widgetSpec: [geometry: [size: [400, 500]]], stack: LIST[menu, XTkWidgets.HRule[], scroller]]; XTkWidgets.SetShellChild[shell, container]; XTkWidgets.RealizeShell[shell]; IO.PutRope[stream, text]; }; XPopupDoneProc: XTkWidgets.ButtonHitProcType = { XTkWidgets.DestroyShell[XTk.RootWidget[widget]]; }; XPopupStuffXProc: XTkWidgets.ButtonHitProcType = { XlCutBuffers.Put[widget.connection, NARROW[registerData]]; }; XPopupRefreshProc: XTkWidgets.ButtonHitProcType = { stream: IO.STREAM ~ NARROW[registerData]; IO.PutChar[stream, 'L - 100B]; IO.PutRope[stream, NARROW[callData]]; }; description: ROPE = "Threads Visualizer\nTV filename\n\t[threadList: ]\n\t[doCedar:]\n\t[timeRange: starttime endtime]\n\t[backgroundThread: ]\n\t[xTransform: realnumber]\n\t[moreInput: filename]"; Commander.Register["ThreadsVis", ThreadsVisCommand, description]; Commander.Register["TV", ThreadsVisCommand, description]; Commander.Register["TVStackPrint", StackPrintCommand, "Threads Visualizer Stack Print Command"]; EmbeddedButtons.RegisterPoppyProc[$TVGetStack, TVGetStack]; EmbeddedButtons.RegisterPoppyProc[$TVPopupXStack, TVPopupXStack]; END. 0 ThreadsVisImpl.mesa Copyright ำ 1992 by Xerox Corporation. All rights reserved. Weiser, March 14, 1993 5:54 pm PST Chauser, December 3, 1992 4:23 pm PST Constants connectCVColor: Imager.Color = ImagerColorFromName["vivid red"]; connectMonitorColor: Imager.Color = ImagerColorFromName["vivid purple"]; connectForkColor: Imager.Color = ImagerColorFromName["vivid green"]; Global Variables (should get rid of these) CommanderProcs don't specify a time range before any additional input files FilterEvents[efacts, efacts.min, efacts.max]; painters algorithm order DrawEvents[sliceQueue, tfacts, efacts, $X]; DrawEvents[sliceQueue, tfacts, efacts, $Y]; DrawEvents[sliceQueue, tfacts, efacts, $Z]; DrawLegend[sliceQueue, efacts.min, -10]; globalThreadFacts _ NIL; Build Gargoyle Scene I've not copied the tree and stackList from "from" into "into. I don't think they're needed here. - chauser IF (e.time*xTransform < efacts.min AND e.time # 0) OR (e.time*xTransform > efacts.max) OR (e.wakeTime*xTransform > efacts.max) THEN RETURN; Unix run events show a process awake. Finish time is when they woke up, and start time is when they went to sleep. Finish <= Start. S events show a process sleeping. Finish time is when they woke up, and start time is when they went to sleep. Start <= Finish. AddCount[e.time*xTransform]; This must be called only after DrawThreads, which initializes the xpos/ypos values in events wakeEvent: ThreadsVisPrivate.Event _ NARROW[tmpEvent]; fixedxstart: REAL; fixedxend: REAL; butInfo.msg _ IO.PutFR["from: %g to: %g", IO.card[wakeEvent.id], IO.card[e.id]]; butInfo.we _ wakeEvent; IF wakeEvent.xstartpos = 0 THEN { An event not being displayed. Fake its position wakeEvent.ypos _ e.ypos+yThreadIncrement/2; wakeEvent.xstartpos _ wakeEvent.xendpos _ e.xendpos; butInfo.msg _ IO.PutFR["from: %g to: %g (thread not shown)", IO.card[wakeEvent.id], IO.card[e.id]]; fake _ TRUE; }; [fixedxstart, cnt, computedThickness] _ GetFixedX[xPosTable, wakeEvent.xstartpos]; IF wakeEvent.xstartpos = e.xendpos THEN fixedxend _ fixedxstart ELSE [fixedxstart, cnt, computedThickness] _ GetFixedX[xPosTable, e.xendpos]; butInfo.msg _ IO.PutFR["from: %g to: %g", IO.card[wakeEvent.id], IO.card[e.id]]; PaintArrow[wakeEvent, fixedxstart, wakeEvent.ypos, fixedxend, e.ypos, fake, cnt, computedThickness]; wakeEvent.drawn _ TRUE; An event not being displayed. Fake its position fixedxstart: REAL; IF NOT e.drawn THEN { butInfo.msg _ IO.PutFR1["from: %g to: NA", IO.rope[GetEventRope[e]]]; [fixedxstart, cnt, computedThickness] _ GetFixedX[xPosTable, e.xstartpos]; PaintArrow[e, fixedxstart, e.ypos, fixedxstart, e.ypos+yThreadIncrement/2, TRUE, cnt, computedThickness]; e.drawn _ TRUE; }; assume a vertical arrow only IO.PutFL[globalOutStream, "drew %g/%g-%g/%g. %g\n", LIST[IO.real[startx], IO.real[starty], IO.real[finishx], IO.real[finishy], IO.rope[GetColorName[color]]]]; PROC [key: Key, val: Val] RETURNS [quit: BOOL ฌ FALSE] Debugging routines for printing things out (unused in normal operation) WalkSlices: PROC [q: ThreadsVisPrivate.Queue] ~ { list: LIST OF GGSlice.Slice _ q.head; UNTIL list = NIL DO IF list.first = NIL THEN CommanderOps.Failed["NIL value in slice list."]; [] _ GGSliceOps.GetType[list.first]; list _ list.rest; ENDLOOP; }; PROC [key: Key, val: Val] RETURNS [quit: BOOL ฌ FALSE] Utilities for various kinds of lists I/O Tricks Button Procs PROC [arguments: LIST OF REF ANY, buttonInfo: ButtonInfo, clientData: REF] RETURNS [REF ANY _ NIL]; PROC [arguments: LIST OF REF ANY, buttonInfo: ButtonInfo, clientData: REF, context: Context] RETURNS [REF ANY _ NIL]; X Popup Windows ส&ต•NewlineDelimiter ™code™Kšœ ฯeœ1™K˜8K˜BK˜Kšœ žœ˜Kšœžœ˜Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜Kšœžœ˜K˜Kšœžœ˜K˜Kšœžœ˜K˜Kšœžœžœ˜K˜Kšœžœžœ ˜Kšœ žœžœžœ˜,K˜Kšœžœžœ ˜Kš œ žœžœžœžœ˜0K˜K˜K˜%Kšœžœžœžœ?˜fK˜—™+K˜Kšœ žœžœ˜K˜Kšœžœžœžœ˜!Kšœ3žœ˜7Kšœ1žœ˜5K˜Kšœžœ˜K˜Kšœ žœ˜Kšœ žœžœ˜,K˜*K˜/Kšœžœ˜2K˜K˜Kš œ žœžœžœžœžœ˜!K˜Kšœžœ˜K˜Kšœ+žœ˜/Kšœžœžœ˜—™K˜šŸœ˜,Kšœ žœ"˜3Kšœ žœKžœ)žœ˜›Kšœžœ˜ K˜Kš œ žœžœžœžœ˜Kšžœžœžœ0˜OKšœ+žœ)˜ZKš žœžœžœžœ&žœ˜aKšœ žœ˜%Kšžœžœ ˜;K˜K˜K˜—šŸœ˜,Kšœ žœ˜šŸ œžœžœžœ˜SK˜K˜—Kš œ žœžœžœžœ˜Kšœ žœ"˜0Kšœžœ˜K˜5Kšœ žœžœ ˜K˜1Kšœžœžœ˜ K˜Kšœžœžœ˜(Kšœ žœžœ˜+K˜%K˜&Kšœ žœ˜Kšœ(žœ˜,Kšœ žœžœ˜Kšœ ž˜K˜K˜šžœ ž˜Kšžœ&˜*Kšžœ˜Kš žœžœžœ žœžœ˜BK˜K˜K˜,Kšžœžœ$˜@Kšœ žœ˜'Kšœsžœ˜zK˜J˜Kšžœ.žœ7˜iK˜K˜.Kšœ žœ˜'šž˜K˜*Kšžœ žœžœžœ˜Kšžœžœž˜šœ$žœžœ˜/Kšœ žœ"˜2Kšœ žœ%˜3Kšœžœ˜ šžœžœ<˜]Kšžœžœžœ˜<—Kšžœžœ ˜!K˜/K˜—šœ!žœžœ˜,Kšœ žœU˜cKšœžœ˜ šžœžœ9˜ZKšœ žœ'žœ˜G—Kšžœžœ ˜!K˜/K˜—šœ#žœžœ˜.Kšœžœ˜ K™˜cKšœžœ™K˜—K˜K˜——™K˜šŸ œžœ/˜@Kšœžœžœ˜(Kšœ žœ˜#Kšœ žœ˜#š žœžœžœ/žœžœž˜MKšœžœ*˜CKšžœ žœžœ ˜"Kšžœ˜—K˜K˜K˜K˜—šŸ œžœ0˜BšŸœ˜$Kšœžœ˜6K˜—Kšœžœžœ˜)š žœžœžœ0žœžœž˜NKšžœ žœžœ ˜ Kšžœ˜—K˜K˜Kšœžœ˜6Kšœœ œF™lK˜K˜—šŸ œžœw˜ˆKšœžœ˜ Kšœžœžœ˜Kšœžœ˜Kšœžœ˜)šŸœžœžœ˜Kšœžœ˜ K˜Kšœžœžœ˜K˜2šžœ˜ šžœ˜Kšœžœ˜K˜K˜—šžœ˜Kšœžœžœ˜Kšœ˜——K˜/K˜—šŸœžœ!žœžœ˜RKšœžœ˜ Kšœžœ˜%Kšœžœ žœ˜1Kšœžœ˜K˜Kšœ žœ˜K˜ Kšžœžœ žœžœžœ&žœžœ˜ŒKš žœ!žœ žœ"žœ&žœžœ™‹šžœžœž˜Kšœžœžœ˜4Kšœžœžœžœžœžœžœžœžœ˜ฆKšœžœ˜6Kšž œ ˜—šžœž˜ ˜K™…K˜K˜šžœžœ˜(šžœ˜K˜]Kšžœžœ^žœ ˜‚K˜——šžœžœ˜'Kšžœj˜n—K˜K˜pK˜rK˜K˜—˜K™K˜K˜šžœ žœ˜K˜K˜xK˜—šžœžœ˜'šžœ˜K˜ZKšžœžœ]žœ ˜K˜——šžœžœ˜'Kšžœi˜m—K˜K˜rK˜rK˜K˜—˜šœžœž˜(K˜K˜K˜Kšžœžœ˜—K˜K˜Kšžœ žœ˜"K˜KšœWžœ ˜eK˜K˜—˜K˜K˜K˜—˜K˜—šžœ˜ Kšžœ=žœ˜PK™K˜——K˜—šŸœžœ"žœžœ˜UK˜K˜K˜šžœ$žœ˜,Kšžœž˜Kšžœžœ˜—K˜K˜—K˜K˜K˜—šŸ œžœzžœ˜“K™\K˜Kšœžœ˜)Kšœžœ˜ Kšœ žœžœ˜Kšœžœ˜š Ÿ œžœ;žœ žœ žœ žœ˜…K˜Kšœžœ!žœ˜4šžœžœž˜K˜NK˜TK˜aKšœ,žœI˜wK˜]K˜>K˜'K˜%Kšœžœ˜=Kšœžœ˜?Kšœ'žœ;˜dšž œ˜ K˜Kšœ8žœ>žœ˜‰K˜——šžœžœ žœžœ˜.Kšžœžœžœ˜IKšžœžœžœ˜VK˜—Kšœdžœ ˜sK˜nK˜—šŸœžœ!žœžœ˜RKšœžœ˜ Kšœžœ˜Kšœžœ˜Kšœžœ žœ˜1K˜Kšœ žœ˜Kš žœ!žœ žœ&žœ"žœžœ˜‹šžœžœžœžœ˜Dš žœžœžœžœžœ˜]Kšžœžœžœžœžœžœžœžœžœžœ˜ฏKšžœ ˜—Kšžœž˜ ˜ Kšœ%žœ ™6Kšœ žœ™Kšœ žœ™Kšœžœžœžœ ™QK™šžœžœ™!K™0K™+K™4Kšœžœ-žœžœ ™dKšœžœ™ K™—K™RKšžœ!žœžœI™Kšœžœžœžœ ™QK™dKšœžœ™K˜—˜Kšœžœžœ˜šžœžœ˜Kšœ"žœ#˜Kšžœžœžœ˜Kšœ8žœ.žœ˜—šœžœ˜Kšœ%žœ ˜6Kšœ žœ˜Kšœ žœ˜Kšœžœžœžœ ˜QK˜šžœžœ˜!K™0K˜+K˜4Kšœžœ-žœžœ ˜dKšœžœ˜ K˜—K˜]Kšžœ!žœžœL˜Kšœžœžœžœ ˜QK˜dKšœžœ˜K˜—K˜—K˜—˜Kšœ žœ˜šžœžœ žœ˜Kšœžœ žœ˜JK˜MKšœKžœ˜iKšœ žœ˜K˜—K˜—˜K˜—˜Kšœ žœ˜šžœžœ žœ˜Kšœžœžœ˜EK˜MKšœKžœ˜iKšœ žœ˜K˜—K˜—šžœ˜ Kšžœ:žœ˜MKšœ žœ™šžœžœ žœ™Kšœžœžœ™EK™JKšœKžœ™iKšœ žœ™K™—K˜—K˜—K˜—šŸœžœ"žœžœ˜UK˜K˜K˜—K˜Kšžœžœ9žœ,žœ˜™K˜K˜—š Ÿ œžœKžœžœžœ˜uKšœ žœ˜Kš œžœžœžœžœ˜Kšœžœžœ"˜EK˜$Kšžœ˜ K˜K˜—šŸœžœ žœ žœžœžœžœžœ žœ˜^šžœžœžœžœžœžœžœž˜@Kš žœžœ1žœžœžœ˜aKšžœ˜—Kšžœžœ˜K˜K˜—šŸ œžœžœžœžœžœ žœ˜yKšœžœ˜ K˜K˜Kšœ žœ˜K˜5Kš žœžœžœžœ=žœ˜Kšœ žœ žœžœ˜'Kšœ žœ&˜5Kšžœ#žœ#˜LK˜#šžœ˜ šžœ˜Kšœžœ˜K˜&K˜K˜—šžœ˜Kšœžœ˜ Kšœ˜——K˜ Kšžœ#˜)K˜K˜K˜K˜—šŸœžœ%žœ"žœžœžœ.žœ˜าK˜šŸœžœžœ˜4K˜K˜K˜K˜—K˜;KšœPžœ˜UK˜K˜,šžœžœ˜K™K˜Kšœžœ˜ Kšžœžœ žœ ˜1KšœQžœ˜VK˜ K˜1KšœQžœ˜VK˜ K˜1KšœQžœ˜VK˜ K˜1K˜—Kšžœžœžœžœ˜2Kšžœ2žœžœžœžœžœžœ™žKšžœ ˜K˜K˜K˜—šŸœžœ-žœžœ4˜~K˜K˜5K˜K˜5Kšœžœ˜ Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜K˜K˜)K˜6K˜#K˜"K˜šžœž˜K˜(Kšœ- ˜EKšžœ žœžœžœ(˜^K˜ K˜BK˜AK˜YKšœ?žœ˜Dšžœž˜K˜&K˜&Kšžœ˜—K˜&Kšžœ˜—K˜K˜—šŸœžœžœžœ˜AK˜aKšžœ ˜K˜K˜—šŸ œžœžœžœ˜>Kšžœžœžœ ˜,Kšžœžœžœ ˜*Kšžœžœžœ ˜0Kšžœžœžœ˜9Kšžœžœžœ˜;Kšžœžœžœ˜AKšžœžœžœ˜CKšžœžœžœ˜7Kšžœ˜K˜K˜—šŸ œžœ7žœ˜PKšœžœ ˜šŸœžœžœ˜K˜-K˜K˜—Kšœ_žœ˜eK˜%K˜KšœMžœ˜SK˜K˜KšœSžœ˜YK˜ K˜KšœOžœ˜UK˜K˜KšœZžœ˜`KšœXžœ˜nK˜K˜Kšœ]žœ˜cKšœ[žœ˜qK˜K˜Kšœ_žœ˜eKšœ]žœ˜sK˜K˜KšœXžœ˜^KšœVžœ˜lK˜K˜Kšœ_žœ˜eKšœ]žœ˜sK˜K˜KšœXžœ˜^KšœVžœ˜lK˜ K˜K˜K˜—šŸ œžœw˜‰Kšœžœ˜ šŸœžœ"žœžœ˜UKšœžœ˜(K˜K˜4K˜—K˜K˜K˜—šŸœžœžœžœ˜Sšžœžœ˜ šžœ˜Kšœ+žœ˜EK˜—šžœ˜K˜Kšœ˜——K˜K˜—šŸ œžœXžœ˜qK˜šžœžœžœž˜?Kšœ žœ]˜mK˜MKšžœ˜—K˜K˜K˜—šŸ œžœžœžœ˜GKšžœžœžœžœ žœžœžœžœžœ˜œK˜K˜—šŸ œžœžœ ž œ˜:šžœ˜ Kšœžœ˜Kšžœžœžœ˜-K˜—K˜Kš œžœžœžœ žœ+˜Mšž˜Kšœžœ˜Kšœžœ˜Kšœ žœžœ'˜;Kšœ žœ˜Kšžœ˜—šž˜Kšœž˜—K˜K˜—šŸœžœ,˜BK˜#K˜ šŸ œ˜&Kšžœžœžœžœ™6šžœžœžœ˜,K˜>Kšžœžœ˜K˜—K˜—šŸ œžœ"žœžœ˜ZK˜ Kšœžœ˜4K˜—šŸ œžœžœžœžœžœžœžœ˜9Kšœžœ˜J˜"K˜—Kšžœžœ#˜:K˜K˜——™Gš Ÿœžœžœžœ žœžœ˜SšŸœžœ!žœžœ˜RKšžœžœ˜.K˜—šŸœžœ"žœžœ˜UKšžœžœ˜2K˜K˜—K˜K˜K˜K˜—šŸ œžœ!™1Kšœžœžœ™%šžœžœž™Kšžœžœžœ1™IK™$K™Kšžœ™—K™K™—šŸœžœ žœžœžœžœžœžœ˜AšŸœžœžœžœžœžœžœžœ˜3Kš œžœžœžœžœžœ˜#Kšžœžœ˜9Kšžœžœ˜.K˜—K˜K˜K˜—šŸ œžœžœžœžœžœžœžœ˜@šŸœžœžœžœžœžœžœžœ˜2Kšœ&žœ˜0Kšœ.žœ˜DK˜—K˜K˜K˜—šŸœžœ žœžœ˜E˜%Kšœ8žœžœ ˜hK˜—K˜K˜šžœžœž˜K˜Kšœ8žœžœžœ˜{Kšœžœ˜0K˜Kšžœ˜—K˜K˜—šŸ œžœžœ˜˜!Kšžœžœžœžœ™6K˜2K˜—Kšžœžœ"˜DKšœžœ˜'Kšžœžœ ˜/K˜K˜—K˜—™$šŸ œžœžœ žœžœžœžœ˜LKšœ žœ˜Kšœ žœ˜ šž˜K˜)Kšžœžœžœ-˜UK˜BKšœ@žœ ˜NK˜Kšžœ˜—K˜K˜—šŸœžœ+˜?šŸ œžœ!žœžœ˜WKšžœ žœ"žœ˜QKšžœžœ*˜@K˜—K˜!K˜)K˜K˜K˜—šŸœžœžœžœžœžœžœ˜[Kšœžœ˜ šžœžœž˜Kšœžœ˜K˜ Kšžœ˜—Kšžœ˜ K˜K˜—š Ÿ œžœžœžœžœ˜HK˜šžœžœž˜K˜ K˜K˜ Kšžœ˜—K˜K˜—šŸœžœžœžœ!žœžœžœ˜šžœžœž˜K˜K˜Kšžœ˜—K˜K˜—šŸœžœžœžœ žœžœžœ˜|šžœžœž˜K˜K˜Kšžœ˜—K˜K˜—šŸœžœžœ(˜AKšœ žœ˜+K˜K˜—šŸ œžœ3˜CJšœžœ˜'K˜—K˜šŸ œžœR˜bJšœžœ ˜K˜K˜—šŸ œžœ3˜BJšœžœ˜'K˜K˜—š Ÿ œžœžœ)žœ žœžœ˜tKšœ žœžœžœ˜1Kšœ žœžœžœ˜0Kšœ žœ˜šŸ œžœ"žœžœ˜Xšžœ2žœ žœ˜KKš žœ žœžœžœžœ˜8šžœ˜Kšœžœžœ˜&Kšœ˜—Kšœ<žœ ˜MK˜—K˜—Kš œžœžœžœžœ˜*Kš œ žœžœžœžœ˜šž˜Kš œ žœžœ žœžœ˜OK˜ Kšžœ˜—Kšžœ žœžœ˜/K˜K˜——™ K˜šŸ œžœ˜(Kšžœž ˜*K˜2K˜K˜—šŸ œžœžœžœ˜$Kšœ žœ˜Kšœžœ˜Kšœ žœ˜šž˜K˜Kš žœ žœžœ žœžœ˜DKšžœ žœ ˜K˜K˜Kšžœžœ%˜=Kšžœ˜—K˜K˜—šŸœžœžœ˜#Kšœ žœ˜K˜——™ Kšœžœ˜%šœžœ˜K˜ๆ—Kšœžœ:˜QK˜šŸœ˜0Jšžœ žœžœžœžœ&žœžœžœžœžœ™dJšœ žœžœ˜Jšœ žœ˜J˜J˜Kšœžœ˜ K˜Kš œ žœžœžœžœ˜šžœžœž˜ Kšœžœžœ˜Kšœžœžœ˜Kšžœ˜—Kšžœžœžœžœ(˜NKšœ&žœ(˜TKš žœžœžœžœžœ&žœ˜jKšœžœ˜Kšœ žœ&žœ˜PKšœ+žœ,˜]Kš žœžœžœžœžœ+žœ˜rKšœ žœ˜%Kšœžœžœ!˜RKšžœ ˜K˜—K˜šŸ œ˜&Jšžœ žœžœžœžœ&žœžœžœžœžœ™vKšžœC˜IK˜K˜—šŸ œ˜)KšœžœžœC˜VK˜%K˜K˜—šŸœžœN˜bšŸœžœžœžœ˜JK˜Kšœ žœ˜Kšœ žœ˜K˜Kš œ žœžœžœžœ˜Kšœžœ˜ Kš žœžœ'žœžœžœ˜?Kš žœžœžœžœžœ˜)Kšœ+žœ4˜eKš žœžœžœžœžœ˜Kšœ žœ˜%šžœžœžœžœžœžœ žœž˜GK˜Kšœ žœ1˜AKšžœ˜—Kšžœ ˜K˜—š Ÿ œžœžœžœžœ˜6Kšžœžœžœžœžœžœžœžœ žœ ˜[K˜—Kšœ žœU˜gKšœžœ˜Kšœ žœ˜Kšžœžœžœ žœ˜BKšžœžœžœ žœ˜>Kšœ(žœžœžœžœžœžœžœ žœ žœ žœ˜ๆK˜/K˜——™Kšœžœ˜K˜šŸ œžœžœ˜(˜'K˜K˜K˜K˜K˜—K˜iKšœžœžœ˜K˜YK˜/K˜GK˜^K˜TKšœ&žœ+˜UKšœsžœ˜zKšœWžœ&˜K˜+K˜Kšžœ˜K˜K˜—šŸœ"˜0K˜0Kšœ˜K˜—šŸœ"˜2Kšœ$žœ˜:K˜K˜—šŸœ"˜3Jšœžœžœžœ˜)Kšžœ˜Kšžœžœ ˜%K˜K˜——˜ืK˜—K˜K˜AK˜9K˜`K˜;K˜AKšžœ˜K˜—…—šๆฯห