func TestHandlerBufferSize(t *testing.T) { base := BaseHandler{} base.log = l.WithField("testing", "basehandler_flush") base.interval = 5 base.maxBufferSize = 100 base.channel = make(chan metric.Metric) base.collectorEndpoints = map[string]CollectorEnd{ "collector1": CollectorEnd{make(chan metric.Metric), 3}, } emitFunc := func(metrics []metric.Metric) bool { assert.Equal(t, 3, len(metrics)) return true } go base.run(emitFunc) base.CollectorEndpoints()["collector1"].Channel <- metric.New("testMetric") base.CollectorEndpoints()["collector1"].Channel <- metric.New("testMetric1") base.CollectorEndpoints()["collector1"].Channel <- metric.New("testMetric2") time.Sleep(1 * time.Second) assert.Equal(t, uint64(3), atomic.LoadUint64(&base.metricsSent)) assert.Equal(t, uint64(0), atomic.LoadUint64(&base.metricsDropped)) // This is just to stop goroutines that have been started before base.channel <- metric.Metric{} base.CollectorEndpoints()["collector1"].Channel <- metric.Metric{} }
func (t *tracker) StatusUpdate() { shardCount := atomic.LoadUint64(&t.stats.CompletedShards) pointCount := atomic.LoadUint64(&t.stats.PointsRead) pointWritten := atomic.LoadUint64(&t.stats.PointsWritten) log.Printf("Still Working: Completed Shards: %d/%d Points read/written: %d/%d", shardCount, len(t.shards), pointCount, pointWritten) }
func (h *Hub) Stat(details bool) *HubStat { h.mutex.RLock() defer h.mutex.RUnlock() stat := &HubStat{ Rooms: len(h.roomTable), Connections: len(h.connectionTable), Sessions: len(h.sessionTable), Count: h.count, BroadcastChatMessages: atomic.LoadUint64(&h.broadcastChatMessages), UnicastChatMessages: atomic.LoadUint64(&h.unicastChatMessages), } if details { rooms := make(map[string][]string) for roomid, room := range h.roomTable { sessions := make([]string, 0, len(room.connections)) for id := range room.connections { sessions = append(sessions, id) } rooms[roomid] = sessions } stat.IdsInRoom = rooms sessions := make(map[string]*DataSession) for sessionid, session := range h.sessionTable { sessions[sessionid] = session.Data() } stat.SessionsById = sessions connections := make(map[string]string) for id, connection := range h.connectionTable { connections[fmt.Sprintf("%d", connection.Idx)] = id } stat.ConnectionsByIdx = connections } return stat }
func (z *zeroSum) monitor(d time.Duration) { start := timeutil.Now() lastTime := start var lastOps uint64 for ticks := 0; true; ticks++ { time.Sleep(d) if ticks%20 == 0 { fmt.Printf("_elapsed__accounts_________ops__ops/sec___errors___splits____xfers___ranges_____________replicas\n") } now := timeutil.Now() elapsed := now.Sub(lastTime).Seconds() ops := atomic.LoadUint64(&z.stats.ops) z.ranges.Lock() ranges, replicas := z.ranges.count, z.ranges.replicas z.ranges.Unlock() fmt.Printf("%8s %9d %11d %8.1f %8d %8d %8d %8d %20s\n", time.Duration(now.Sub(start).Seconds()+0.5)*time.Second, z.accountsLen(), ops, float64(ops-lastOps)/elapsed, atomic.LoadUint64(&z.stats.errors), atomic.LoadUint64(&z.stats.splits), atomic.LoadUint64(&z.stats.transfers), ranges, z.formatReplicas(replicas)) lastTime = now lastOps = ops } }
func getAllGauges() ([]IntMetric, []FloatMetric) { numIDs := int(atomic.LoadUint32(curIntGaugeID)) retint := make([]IntMetric, numIDs) for i := 0; i < numIDs; i++ { retint[i] = IntMetric{ Name: intgnames[i], Val: atomic.LoadUint64(&intgauges[i]), Tgs: intgtags[i], } } numIDs = int(atomic.LoadUint32(curFloatGaugeID)) retfloat := make([]FloatMetric, numIDs) for i := 0; i < numIDs; i++ { // The int64 bit pattern of the float value needs to be converted back // into a float64 here. This is a literal reinterpretation of the same // exact bits. intval := atomic.LoadUint64(&floatgauges[i]) retfloat[i] = FloatMetric{ Name: floatgnames[i], Val: math.Float64frombits(intval), Tgs: floatgtags[i], } } return retint, retfloat }
// mangedLogError will take an error and log it to the host, depending on the // type of error and whether or not the DEBUG flag has been set. func (h *Host) managedLogError(err error) { // Determine the type of error and the number of times that this error has // been logged. var num uint64 var probability int // Error will be logged with 1/probability chance. switch err.(type) { case ErrorCommunication: num = atomic.LoadUint64(&h.atomicCommunicationErrors) probability = errorCommunicationProbability case ErrorConnection: num = atomic.LoadUint64(&h.atomicConnectionErrors) probability = errorConnectionProbability case ErrorConsensus: num = atomic.LoadUint64(&h.atomicConsensusErrors) probability = errorConsensusProbability case ErrorInternal: num = atomic.LoadUint64(&h.atomicInternalErrors) probability = errorInternalProbability default: num = atomic.LoadUint64(&h.atomicNormalErrors) probability = errorNormalProbability } // If num > logFewLimit, substantially decrease the probability that the error // gets logged. if num > logFewLimit { probability = probability * 25 } // If we've seen less than logAllLimit of that type of error before, log // the error as a normal logging statement. Otherwise, probabilistically // log the statement. In debugging mode, log all statements. logged := false rand, randErr := crypto.RandIntn(probability + 1) if randErr != nil { h.log.Critical("random number generation failed") } if num < logAllLimit || rand == probability { logged = true h.log.Println(err) } else { h.log.Debugln(err) } // If the error was logged, increment the log counter. if logged { switch err.(type) { case ErrorCommunication: atomic.AddUint64(&h.atomicCommunicationErrors, 1) case ErrorConnection: atomic.AddUint64(&h.atomicConnectionErrors, 1) case ErrorConsensus: atomic.AddUint64(&h.atomicConsensusErrors, 1) case ErrorInternal: atomic.AddUint64(&h.atomicInternalErrors, 1) default: atomic.AddUint64(&h.atomicNormalErrors, 1) } } }
func (s *IDGenerator) GetStream() (int, bool) { // based closely on the java-driver stream ID generator // avoid false sharing subsequent requests. offset := atomic.LoadUint32(&s.offset) for !atomic.CompareAndSwapUint32(&s.offset, offset, (offset+1)%s.numBuckets) { offset = atomic.LoadUint32(&s.offset) } offset = (offset + 1) % s.numBuckets for i := uint32(0); i < s.numBuckets; i++ { pos := int((i + offset) % s.numBuckets) bucket := atomic.LoadUint64(&s.streams[pos]) if bucket == math.MaxUint64 { // all streams in use continue } for j := 0; j < bucketBits; j++ { mask := uint64(1 << streamOffset(j)) if bucket&mask == 0 { if atomic.CompareAndSwapUint64(&s.streams[pos], bucket, bucket|mask) { atomic.AddInt32(&s.inuseStreams, 1) return streamFromBucket(int(pos), j), true } bucket = atomic.LoadUint64(&s.streams[offset]) } } } return 0, false }
func (v *signatureVerifier) metrics() []instrumentation.Metric { return []instrumentation.Metric{ instrumentation.Metric{Name: "missingSignatureErrors", Value: atomic.LoadUint64(&v.missingSignatureErrorCount)}, instrumentation.Metric{Name: "invalidSignatureErrors", Value: atomic.LoadUint64(&v.invalidSignatureErrorCount)}, instrumentation.Metric{Name: "validSignatures", Value: atomic.LoadUint64(&v.validSignatureCount)}, } }
func (s *ProxyServer) collectMinersStats() (int64, int, []interface{}) { now := util.MakeTimestamp() var result []interface{} totalHashrate := int64(0) totalOnline := 0 for m := range s.miners.Iter() { stats := make(map[string]interface{}) lastBeat := m.Val.getLastBeat() hashrate := m.Val.hashrate() totalHashrate += hashrate stats["name"] = m.Key stats["hashrate"] = hashrate stats["lastBeat"] = lastBeat stats["validShares"] = atomic.LoadUint64(&m.Val.validShares) stats["invalidShares"] = atomic.LoadUint64(&m.Val.invalidShares) stats["validBlocks"] = atomic.LoadUint64(&m.Val.validBlocks) stats["invalidBlocks"] = atomic.LoadUint64(&m.Val.invalidBlocks) stats["ip"] = m.Val.IP if now-lastBeat > (int64(s.timeout/2) / 1000000) { stats["warning"] = true } if now-lastBeat > (int64(s.timeout) / 1000000) { stats["timeout"] = true } else { totalOnline++ } result = append(result, stats) } return totalHashrate, totalOnline, result }
// Dequeue removes and returns the `oldest` element from the ring buffer // It also returns true if the operation is successful, false otherwise // It blocks on an empty queue func (rb *RingBuffer) Dequeue() (data interface{}, b bool) { var cell *ring_cell pos := atomic.LoadUint64(&rb.dequeue_pos_) i := 0 Loop: for { cell = rb.buffer_[pos&rb.buffer_mask_] seq := atomic.LoadUint64(&cell.sequence_) switch dif := seq - pos - 1; { case dif == 0: if atomic.CompareAndSwapUint64(&rb.dequeue_pos_, pos, pos+1) { break Loop } case dif < 0: return nil, false default: pos = atomic.LoadUint64(&rb.dequeue_pos_) } // freeup the cpu if i >= freeup_threshold { runtime.Gosched() i = 0 } else { i++ } } data = cell.data_ atomic.StoreUint64(&cell.sequence_, pos+rb.buffer_mask_+1) b = true return data, b }
// Enqueue adds a new element to the tail of the ring buffer // It returns true if the operation is successful, false otherwise // It blocks on a full queue func (rb *RingBuffer) Enqueue(data interface{}) bool { var cell *ring_cell pos := atomic.LoadUint64(&rb.enqueue_pos_) i := 0 Loop: for { cell = rb.buffer_[pos&rb.buffer_mask_] seq := atomic.LoadUint64(&cell.sequence_) switch dif := seq - pos; { case dif == 0: if atomic.CompareAndSwapUint64(&rb.enqueue_pos_, pos, pos+1) { break Loop } case dif < 0: return false default: pos = atomic.LoadUint64(&rb.enqueue_pos_) } // freeup the cpu if i >= freeup_threshold { runtime.Gosched() i = 0 } else { i++ } } cell.data_ = data atomic.StoreUint64(&cell.sequence_, pos+1) return true }
func printLine() { // get timeNow := time.Now() nowTotalIndexed := atomic.LoadUint64(&totalIndexed) nowTotalPlainTextIndexed := atomic.LoadUint64(&totalPlainTextIndexed) // calculate curPlainTextIndexed := nowTotalPlainTextIndexed - lastTotalPlainTextIndexed cumTimeTaken := timeNow.Sub(timeStart) curTimeTaken := timeNow.Sub(timeLast) cumMBytes := float64(nowTotalPlainTextIndexed) / 1000000.0 curMBytes := float64(curPlainTextIndexed) / 1000000.0 cumSeconds := float64(cumTimeTaken) / float64(time.Second) curSeconds := float64(curTimeTaken) / float64(time.Second) dateNow := timeNow.Format(time.RFC3339) fmt.Fprintf(statsWriter, "%s,%d,%d,%f,%f\n", dateNow, nowTotalIndexed, nowTotalPlainTextIndexed, cumMBytes/cumSeconds, curMBytes/curSeconds) timeLast = timeNow lastTotalIndexed = nowTotalIndexed lastTotalPlainTextIndexed = nowTotalPlainTextIndexed }
func (u *dropsondeUnmarshaller) metrics() []instrumentation.Metric { var metrics []instrumentation.Metric u.RLock() for appID, count := range u.logMessageReceiveCounts { metricValue := atomic.LoadUint64(count) tags := make(map[string]interface{}) tags["appId"] = appID metrics = append(metrics, instrumentation.Metric{Name: "logMessageReceived", Value: metricValue, Tags: tags}) } metricValue := atomic.LoadUint64(u.receiveCounts[events.Envelope_LogMessage]) metrics = append(metrics, instrumentation.Metric{Name: "logMessageTotal", Value: metricValue}) u.RUnlock() for eventType, counterPointer := range u.receiveCounts { if eventType == events.Envelope_LogMessage { continue } modifiedEventName := []rune(eventType.String()) modifiedEventName[0] = unicode.ToLower(modifiedEventName[0]) metricName := string(modifiedEventName) + "Received" metricValue := atomic.LoadUint64(counterPointer) metrics = append(metrics, instrumentation.Metric{Name: metricName, Value: metricValue}) } metrics = append(metrics, instrumentation.Metric{ Name: "unmarshalErrors", Value: atomic.LoadUint64(&u.unmarshalErrorCount), }) return metrics }
func TestHandlerRun(t *testing.T) { var mu sync.Mutex base := BaseHandler{} base.log = l.WithField("testing", "basehandler_run") base.interval = 1 base.maxBufferSize = 1 base.channel = make(chan metric.Metric) emitCalled := false emitFunc := func(metrics []metric.Metric) bool { assert.Equal(t, 1, len(metrics)) mu.Lock() defer mu.Unlock() emitCalled = true return true } // now we are waiting for some metrics go base.run(emitFunc) base.channel <- metric.New("testMetric") time.Sleep(1 * time.Second) mu.Lock() assert.True(t, emitCalled) mu.Unlock() assert.Equal(t, 1, base.GetEmissionTimesLen()) assert.Equal(t, uint64(1), atomic.LoadUint64(&base.metricsSent)) assert.Equal(t, uint64(0), atomic.LoadUint64(&base.metricsDropped)) assert.Equal(t, uint64(1), atomic.LoadUint64(&base.totalEmissions)) base.channel <- metric.Metric{} }
func TestManyMessagesSingleSocket(t *testing.T) { count := 1000 interval := 100 * time.Microsecond var serverMsgReceivedCount uint64 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { sck, err := NewSocket("test_socket", w, r, func(messageType int, message []byte) { atomic.AddUint64(&serverMsgReceivedCount, 1) assert.Equal(t, "You got a message!", string(message)) }, nil) if err != nil { t.Error("Error creating websocket:", err) } // when a request comes in, start writing lotsa messages to it go writeLotsaMessages(sck, count, interval) })) defer ts.Close() var clientMsgReceivedCount uint64 client := makeClient(t, ts.URL, "test_client", func(messageType int, message []byte) { atomic.AddUint64(&clientMsgReceivedCount, 1) assert.Equal(t, "You got a message!", string(message)) }, nil) // we opened a client connection, starting sending lotsa messages to the server go writeLotsaMessages(client, count, interval) // sleep a bit to let the messages be sent time.Sleep(3 * time.Second) assert.Equal(t, uint64(count), atomic.LoadUint64(&clientMsgReceivedCount)) assert.Equal(t, uint64(count), atomic.LoadUint64(&serverMsgReceivedCount)) }
// ListPeers returns a verbose listing of all currently active peers. func (r *rpcServer) ListPeers(ctx context.Context, in *lnrpc.ListPeersRequest) (*lnrpc.ListPeersResponse, error) { rpcsLog.Tracef("[listpeers] request") serverPeers := r.server.Peers() resp := &lnrpc.ListPeersResponse{ Peers: make([]*lnrpc.Peer, 0, len(serverPeers)), } for _, serverPeer := range serverPeers { // TODO(roasbeef): add a snapshot method which grabs peer read mtx nodePub := serverPeer.addr.IdentityKey.SerializeCompressed() peer := &lnrpc.Peer{ PubKey: hex.EncodeToString(nodePub), PeerId: serverPeer.id, Address: serverPeer.conn.RemoteAddr().String(), Inbound: serverPeer.inbound, BytesRecv: atomic.LoadUint64(&serverPeer.bytesReceived), BytesSent: atomic.LoadUint64(&serverPeer.bytesSent), } resp.Peers = append(resp.Peers, peer) } rpcsLog.Debugf("[listpeers] yielded %v peers", serverPeers) return resp, nil }
func (u *EventUnmarshaller) metrics() []instrumentation.Metric { var metrics []instrumentation.Metric metricValue := atomic.LoadUint64(u.receiveCounts[events.Envelope_LogMessage]) metrics = append(metrics, instrumentation.Metric{Name: logMessageTotal, Value: metricValue}) for eventType, counterPointer := range u.receiveCounts { if eventType == events.Envelope_LogMessage { continue } modifiedEventName := []rune(eventType.String()) modifiedEventName[0] = unicode.ToLower(modifiedEventName[0]) metricName := string(modifiedEventName) + "Received" metricValue := atomic.LoadUint64(counterPointer) metrics = append(metrics, instrumentation.Metric{Name: metricName, Value: metricValue}) } metrics = append(metrics, instrumentation.Metric{ Name: unmarshalErrors, Value: atomic.LoadUint64(&u.unmarshalErrorCount), }) metrics = append(metrics, instrumentation.Metric{ Name: unknownEvents, Value: atomic.LoadUint64(&u.unknownEventTypeCount), }) return metrics }
func snapshot() { var last = new(stats) for { time.Sleep(time.Minute) requests := atomic.LoadUint64(&Stats.requests) cacheHits := atomic.LoadUint64(&Stats.cacheHits) cacheMisses := atomic.LoadUint64(&Stats.cacheMisses) ch := cacheHits - last.cacheHits ct := cacheMisses - last.cacheMisses + ch if ct == 0 { ct = 1 } s := []byte(fmt.Sprintf(`{"requests":%d,"cacheRatio":%d,"goroutines":%d}`, requests-last.requests, ch*100/ct, runtime.NumGoroutine())) last.requests = requests last.cacheHits = cacheHits last.cacheMisses = cacheMisses file, _ := os.Create("stats.json") file.Write(s) file.Close() } }
func (s *IDGenerator) Clear(stream int) (inuse bool) { offset := bucketOffset(stream) bucket := atomic.LoadUint64(&s.streams[offset]) mask := uint64(1) << streamOffset(stream) if bucket&mask != mask { // already cleared return false } for !atomic.CompareAndSwapUint64(&s.streams[offset], bucket, bucket & ^mask) { bucket = atomic.LoadUint64(&s.streams[offset]) if bucket&mask != mask { // already cleared return false } } // TODO: make this account for 0 stream being reserved if atomic.AddInt32(&s.inuseStreams, -1) < 0 { // TODO(zariel): remove this panic("negative streams inuse") } return true }
func snapshot(c *Configuration) { var last = new(stats) buffer := new(bytes.Buffer) for { time.Sleep(c.statsSleep) requests := atomic.LoadUint64(&Stats.requests) errors := atomic.LoadUint64(&Stats.errors) fatals := atomic.LoadUint64(&Stats.fatals) buffer.Reset() buffer.WriteString(`{"requests":` + strconv.FormatUint(requests-last.requests, 10)) buffer.WriteString(`,"errors":` + strconv.FormatUint(errors-last.errors, 10)) buffer.WriteString(`,"fatals":` + strconv.FormatUint(fatals-last.fatals, 10)) buffer.WriteString(`,"goroutines":` + strconv.Itoa(runtime.NumGoroutine())) buffer.WriteString("}") last.requests = requests last.errors = errors last.fatals = fatals file, e := os.Create(c.statsFile) if e != nil { log.Println("AUWFG could not write stats: ", e) } else { file.Write(buffer.Bytes()) file.Close() } } }
func (pipeline *mk_itemPipeline) Count() []uint64 { counts := make([]uint64, 3) counts[0] = atomic.LoadUint64(&pipeline.sent) counts[1] = atomic.LoadUint64(&pipeline.accepted) counts[2] = atomic.LoadUint64(&pipeline.processed) return counts }
// Put adds the provided item to the queue. If the queue is full, this // call will block until an item is added to the queue or Dispose is called // on the queue. An error will be returned if the queue is disposed. func (rb *RingBuffer) Put(item interface{}) error { var n *node pos := atomic.LoadUint64(&rb.queue) i := 0 L: for { if atomic.LoadUint64(&rb.disposed) == 1 { return disposedError } n = rb.nodes[pos&rb.mask] seq := atomic.LoadUint64(&n.position) switch dif := seq - pos; { case dif == 0: if atomic.CompareAndSwapUint64(&rb.queue, pos, pos+1) { break L } case dif < 0: panic(`Ring buffer in a compromised state during a put operation.`) default: pos = atomic.LoadUint64(&rb.queue) } if i == 10000 { runtime.Gosched() // free up the cpu before the next iteration i = 0 } else { i++ } } n.data = item atomic.StoreUint64(&n.position, pos+1) return nil }
func (this *myItemPipeline) Count() []uint64 { counts := make([]uint64, 3) counts[0] = atomic.LoadUint64(&this.sent) counts[1] = atomic.LoadUint64(&this.accepted) counts[2] = atomic.LoadUint64(&this.processed) return counts }
// // 获取计数器 // func (this *GatewayBackend) GetCounter() (inPack, inByte, outPack, outByte uint64) { inPack = atomic.LoadUint64(&this.inPack) inByte = atomic.LoadUint64(&this.inByte) outPack = atomic.LoadUint64(&this.outPack) outByte = atomic.LoadUint64(&this.outByte) return }
// atomically adds incr to val, returns new val func incrementAndGet(val *uint64, incr uint64) uint64 { currVal := atomic.LoadUint64(val) for !atomic.CompareAndSwapUint64(val, currVal, currVal+incr) { currVal = atomic.LoadUint64(val) } return currVal + incr }
func (is *IndexStat) MarshalJSON() ([]byte, error) { m := map[string]interface{}{} m["index"] = is.indexStat m["searches"] = atomic.LoadUint64(&is.searches) m["search_time"] = atomic.LoadUint64(&is.searchTime) return json.Marshal(m) }
// Get will return the next item in the queue. This call will block // if the queue is empty. This call will unblock when an item is added // to the queue or Dispose is called on the queue. An error will be returned // if the queue is disposed. func (rb *RingBuffer) Get() (interface{}, error) { var n *node pos := atomic.LoadUint64(&rb.dequeue) i := 0 L: for { if atomic.LoadUint64(&rb.disposed) == 1 { return nil, ErrDisposed } n = rb.nodes[pos&rb.mask] seq := atomic.LoadUint64(&n.position) switch dif := seq - (pos + 1); { case dif == 0: if atomic.CompareAndSwapUint64(&rb.dequeue, pos, pos+1) { break L } case dif < 0: panic(`Ring buffer in compromised state during a get operation.`) default: pos = atomic.LoadUint64(&rb.dequeue) } if i == 10000 { runtime.Gosched() // free up the cpu before the next iteration i = 0 } else { i++ } } data := n.data n.data = nil atomic.StoreUint64(&n.position, pos+rb.mask+1) return data, nil }
func (is *IndexStat) statsMap() map[string]interface{} { m := map[string]interface{}{} m["index"] = is.i.i.StatsMap() m["searches"] = atomic.LoadUint64(&is.searches) m["search_time"] = atomic.LoadUint64(&is.searchTime) return m }
func (c *ClientV2) Stats() ClientStats { c.RLock() // TODO: deprecated, remove in 1.0 name := c.ClientID clientId := c.ClientID hostname := c.Hostname userAgent := c.UserAgent c.RUnlock() return ClientStats{ // TODO: deprecated, remove in 1.0 Name: name, Version: "V2", RemoteAddress: c.RemoteAddr().String(), ClientID: clientId, Hostname: hostname, UserAgent: userAgent, State: atomic.LoadInt32(&c.State), ReadyCount: atomic.LoadInt64(&c.ReadyCount), InFlightCount: atomic.LoadInt64(&c.InFlightCount), MessageCount: atomic.LoadUint64(&c.MessageCount), FinishCount: atomic.LoadUint64(&c.FinishCount), RequeueCount: atomic.LoadUint64(&c.RequeueCount), ConnectTime: c.ConnectTime.Unix(), SampleRate: atomic.LoadInt32(&c.SampleRate), TLS: atomic.LoadInt32(&c.TLS) == 1, Deflate: atomic.LoadInt32(&c.Deflate) == 1, Snappy: atomic.LoadInt32(&c.Snappy) == 1, } }
// extraServiceInfo implements the profiler.ExtraServiceInfoRetriever interface, // returning a map of key/value pairs of diagnostic information. func extraServiceInfo() map[string]interface{} { extraInfo := make(map[string]interface{}) extraInfo["uptime"] = time.Now().Round(time.Second).Sub(startTime).String() extraInfo["hit count: /profiler/info.html"] = atomic.LoadUint64(&infoHTMLHitCount) extraInfo["hit count: /profiler/info"] = atomic.LoadUint64(&infoHitCount) return extraInfo }